diff --git a/.github/ci/build-xtensa.sh b/.github/ci/build-xtensa.sh new file mode 100755 index 000000000..103575bc9 --- /dev/null +++ b/.github/ci/build-xtensa.sh @@ -0,0 +1,37 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target + +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + +cargo install espup +/ci/cache/cargo/bin/espup install --toolchain-version 1.84.0.0 + +# Restore lockfiles +if [ -f /ci/cache/lockfiles.tar ]; then + echo Restoring lockfiles... + tar xf /ci/cache/lockfiles.tar +fi + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +mkdir .cargo +cat > .cargo/config.toml<< EOF +[unstable] +build-std = ["alloc", "core"] +EOF + +./ci-xtensa.sh + +# Save lockfiles +echo Saving lockfiles... +find . -type f -name Cargo.lock -exec tar -cf /ci/cache/lockfiles.tar '{}' \+ diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 47babe8f5..58ffe5f2e 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh @@ -25,6 +25,7 @@ docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.z docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup +docserver-builder -i ./embassy-mspm0 -o webroot/crates/embassy-mspm0/git.zup docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup @@ -32,7 +33,7 @@ docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/g docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup docserver-builder -i ./embassy-time-driver -o webroot/crates/embassy-time-driver/git.zup -docserver-builder -i ./embassy-time-queue-driver -o webroot/crates/embassy-time-queue-driver/git.zup +docserver-builder -i ./embassy-time-queue-utils -o webroot/crates/embassy-time-queue-utils/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 diff --git a/.github/ci/test-nightly.sh b/.github/ci/test-nightly.sh index 1724ffe89..a03b55e8d 100755 --- a/.github/ci/test-nightly.sh +++ b/.github/ci/test-nightly.sh @@ -9,6 +9,9 @@ export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target mv rust-toolchain-nightly.toml rust-toolchain.toml +cargo test --manifest-path ./embassy-executor/Cargo.toml +cargo test --manifest-path ./embassy-executor/Cargo.toml --features nightly + MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-executor/Cargo.toml --features nightly MIRIFLAGS=-Zmiri-ignore-leaks cargo miri test --manifest-path ./embassy-sync/Cargo.toml diff --git a/.github/ci/test.sh b/.github/ci/test.sh index da0021684..c9b332cf8 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -8,11 +8,16 @@ export RUSTUP_HOME=/ci/cache/rustup export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + +cargo test --manifest-path ./embassy-executor/Cargo.toml cargo test --manifest-path ./embassy-futures/Cargo.toml cargo test --manifest-path ./embassy-sync/Cargo.toml cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml cargo test --manifest-path ./embassy-hal-internal/Cargo.toml -cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue,mock-driver +cargo test --manifest-path ./embassy-time/Cargo.toml --features mock-driver,embassy-time-queue-utils/generic-queue-8 cargo test --manifest-path ./embassy-time-driver/Cargo.toml cargo test --manifest-path ./embassy-boot/Cargo.toml @@ -24,8 +29,10 @@ cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --feat cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp2040,_test cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver,rp235xa,_test -cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,exti,time-driver-any,exti -cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,exti,time-driver-any,exti -cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,exti,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,time-driver-any,exti,single-bank +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f429vg,time-driver-any,exti,dual-bank +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f732ze,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,time-driver-any,exti,single-bank +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features stm32f769ni,time-driver-any,exti,dual-bank cargo test --manifest-path ./embassy-net-adin1110/Cargo.toml diff --git a/.gitignore b/.gitignore index 1c221e876..2d7cbc4fe 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,8 @@ Cargo.lock third_party /Cargo.toml out/ +# editor artifacts +.zed +.neoconf.json +*.vim +**/.idea \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 48d0957e6..e4814ff27 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,6 +28,11 @@ // To work on the examples, comment the line above and all of the cargo.features lines, // then uncomment ONE line below to select the chip you want to work on. // This makes rust-analyzer work on the example crate and all its dependencies. + // "examples/mspm0c1104/Cargo.toml", + // "examples/mspm0g3507/Cargo.toml", + // "examples/mspm0g3519/Cargo.toml", + // "examples/mspm0l1306/Cargo.toml", + // "examples/mspm0l2228/Cargo.toml", // "examples/nrf52840-rtic/Cargo.toml", // "examples/nrf5340/Cargo.toml", // "examples/nrf-rtos-trace/Cargo.toml", diff --git a/README.md b/README.md index 8952b0358..68d8460d3 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,55 @@ # Embassy -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. +Embassy is the next-generation framework for embedded applications. Write safe, correct, and energy-efficient embedded code faster, using the Rust programming language, its async facilities, and the Embassy libraries. + +## [Documentation](https://embassy.dev/book/index.html) - [API reference](https://docs.embassy.dev/) - [Website](https://embassy.dev/) - [Chat](https://matrix.to/#/#embassy-rs:matrix.org) -## Documentation - API reference - Website - Chat ## Rust + async ❤️ embedded -The Rust programming language is blazingly fast and memory-efficient, with no runtime, garbage collector or OS. It catches a wide variety of bugs at compile time, thanks to its full memory- and thread-safety, and expressive type system. +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. -Rust's async/await allows for unprecedentedly easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation, and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is faster and smaller than one! +Rust's [async/await](https://rust-lang.github.io/async-book/) allows for unprecedentedly easy and efficient multitasking in embedded systems. Tasks get transformed at compile time into state machines that get run cooperatively. It requires no dynamic memory allocation and runs on a single stack, so no per-task stack size tuning is required. It obsoletes the need for a traditional RTOS with kernel context switching, and is [faster and smaller than one!](https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown) ## Batteries included -- **Hardware Abstraction Layers** - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy. - - embassy-stm32, for all STM32 microcontroller families. - - embassy-nrf, for the Nordic Semiconductor nRF52, nRF53, nRF91 series. - - embassy-rp, for the Raspberry Pi RP2040 microcontroller. - - esp-rs, for the Espressif Systems ESP32 series of chips. - - Embassy HAL support for Espressif chips is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository. - - Async WiFi, Bluetooth and ESP-NOW is being developed in the [esp-rs/esp-wifi](https://github.com/esp-rs/esp-wifi) repository. - - ch32-hal, for the WCH 32-bit RISC-V(CH32V) series of chips. +- **Hardware Abstraction Layers** + - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy. + - [embassy-stm32](https://docs.embassy.dev/embassy-stm32/), for all STM32 microcontroller families. + - [embassy-nrf](https://docs.embassy.dev/embassy-nrf/), for the Nordic Semiconductor nRF52, nRF53, nRF54 and nRF91 series. + - [embassy-rp](https://docs.embassy.dev/embassy-rp/), for the Raspberry Pi RP2040 and RP23xx microcontrollers. + - [embassy-mspm0](https://docs.embassy.dev/embassy-mspm0/), for the Texas Instruments MSPM0 microcontrollers. + - [esp-rs](https://github.com/esp-rs), for the Espressif Systems ESP32 series of chips. + - Embassy HAL support for Espressif chips, as well as Async Wi-Fi, Bluetooth, and ESP-NOW, is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository. + - [ch32-hal](https://github.com/ch32-rs/ch32-hal), for the WCH 32-bit RISC-V(CH32V) series of chips. + - [mpfs-hal](https://github.com/AlexCharlton/mpfs-hal), for the Microchip PolarFire SoC. + - [py32-hal](https://github.com/py32-rs/py32-hal), for the Puya Semiconductor PY32 series of microcontrollers. -- **Time that Just Works** - -No more messing with hardware timers. embassy_time provides Instant, Duration and Timer types that are globally available and never overflow. +- **Time that Just Works** - + No more messing with hardware timers. [embassy_time](https://docs.embassy.dev/embassy-time) provides Instant, Duration, and Timer types that are globally available and never overflow. -- **Real-time ready** - -Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the example. +- **Real-time ready** - + Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities so that higher priority tasks preempt lower priority ones. See the [example](https://github.com/embassy-rs/embassy/blob/master/examples/nrf52840/src/bin/multiprio.rs). -- **Low-power ready** - -Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting. - -- **Networking** - -The embassy-net network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently. +- **Low-power ready** - + Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting. -- **Bluetooth** - -The nrf-softdevice crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. -The embassy-stm32-wpan crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers. +- **Networking** - + The [embassy-net](https://docs.embassy.dev/embassy-net/) network stack implements extensive networking functionality, including Ethernet, IP, TCP, UDP, ICMP, and DHCP. Async drastically simplifies managing timeouts and serving multiple connections concurrently. -- **LoRa** - The lora-rs project provides an async LoRa and LoRaWAN stack that works well on Embassy. +- **Bluetooth** + - The [trouble](https://github.com/embassy-rs/trouble) crate provides a Bluetooth Low Energy 4.x and 5.x Host that runs on any microcontroller implementing the [bt-hci](https://github.com/embassy-rs/bt-hci) traits (currently + `nRF52`, `rp2040`, `rp23xx` and `esp32` and `serial` controllers are supported). + - The [nrf-softdevice](https://github.com/embassy-rs/nrf-softdevice) crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. + - The [embassy-stm32-wpan](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32-wpan) crate provides Bluetooth Low Energy 5.x support for stm32wb microcontrollers. -- **USB** - -embassy-usb implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own. +- **LoRa** - + The [lora-rs](https://github.com/lora-rs/lora-rs) project provides an async LoRa and LoRaWAN stack that works well on Embassy. -- **Bootloader and DFU** - -embassy-boot is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. +- **USB** - + [embassy-usb](https://docs.embassy.dev/embassy-usb/) implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own. +- **Bootloader and DFU** - + [embassy-boot](https://github.com/embassy-rs/embassy/tree/master/embassy-boot) is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. ## Sneak peek @@ -52,11 +58,11 @@ use defmt::info; use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; -use embassy_nrf::Peripherals; +use embassy_nrf::{Peri, Peripherals}; // Declare async tasks #[embassy_executor::task] -async fn blink(pin: AnyPin) { +async fn blink(pin: Peri<'static, AnyPin>) { let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); loop { @@ -74,7 +80,7 @@ async fn main(spawner: Spawner) { let p = embassy_nrf::init(Default::default()); // Spawned tasks run in the background, concurrently. - spawner.spawn(blink(p.P0_13.degrade())).unwrap(); + spawner.spawn(blink(p.P0_13.into())).unwrap(); let mut button = Input::new(p.P0_11, Pull::Up); loop { @@ -90,13 +96,15 @@ async fn main(spawner: Spawner) { ## Examples -Examples are found in the `examples/` folder separated by the chip manufacturer they are designed to run on. For example: +Examples are found in the +`examples/` folder separated by the chip manufacturer they are designed to run on. For example: -* `examples/nrf52840` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards. -* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095). -* `examples/stm32xx` for the various STM32 families. -* `examples/rp` are for the RP2040 chip. -* `examples/std` are designed to run locally on your PC. +* `examples/nrf52840` run on the + `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards. +* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095). +* `examples/stm32xx` for the various STM32 families. +* `examples/rp` are for the RP2040 chip. +* `examples/std` are designed to run locally on your PC. ### Running examples @@ -123,7 +131,7 @@ cargo run --release --bin blinky For more help getting started, see [Getting Started][1] and [Running the Examples][2]. -## Developing Embassy with Rust Analyzer based editors +## Developing Embassy with Rust Analyzer-based editors The [Rust Analyzer](https://rust-analyzer.github.io/) is used by [Visual Studio Code](https://code.visualstudio.com/) and others. Given the multiple targets that Embassy serves, there is no Cargo workspace file. Instead, the Rust Analyzer @@ -133,7 +141,7 @@ please refer to the `.vscode/settings.json` file's `rust-analyzer.linkedProjects ## Minimum supported Rust version (MSRV) Embassy is guaranteed to compile on stable Rust 1.75 and up. It *might* -compile with older versions but that may change in any new patch release. +compile with older versions, but that may change in any new patch release. ## Why the name? diff --git a/ci-nightly.sh b/ci-nightly.sh index bdb364f53..d486d442c 100755 --- a/ci-nightly.sh +++ b/ci-nightly.sh @@ -13,20 +13,14 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt,arch-cortex-m,executor-thread,executor-interrupt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-interrupt,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32 \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread \ - --- 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 \ + --- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/nrf52840-rtic \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7a-none-eabi --features nightly,arch-cortex-ar,executor-thread \ -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 +RUSTFLAGS="$RUSTFLAGS -C target-cpu=atmega328p" cargo build --release --manifest-path embassy-executor/Cargo.toml --target avr-none -Z build-std=core,alloc --features nightly,arch-avr,avr-device/atmega328p diff --git a/ci-xtensa.sh b/ci-xtensa.sh new file mode 100755 index 000000000..6c9807e98 --- /dev/null +++ b/ci-xtensa.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +set -eo pipefail + +export RUSTFLAGS=-Dwarnings +export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info +export RUSTUP_TOOLCHAIN=esp +if [[ -z "${CARGO_TARGET_DIR}" ]]; then + export CARGO_TARGET_DIR=target_ci +fi + +cargo batch \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features log \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features defmt,arch-spin,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,arch-spin,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32s3-none-elf --features defmt,arch-spin,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,rtos-trace \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target xtensa-esp32-none-elf --features arch-spin,executor-thread \ + --- build --release --manifest-path embassy-sync/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt \ + --- build --release --manifest-path embassy-time/Cargo.toml --target xtensa-esp32s2-none-elf --features defmt,defmt-timestamp-uptime,mock-driver \ + --- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target xtensa-esp32s2-none-elf \ + --- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target xtensa-esp32s2-none-elf --features generic-queue-8 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ieee802154 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,medium-ieee802154 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target xtensa-esp32-none-elf --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \ diff --git a/ci.sh b/ci.sh index 3e3d9ad00..e1fdf998a 100755 --- a/ci.sh +++ b/ci.sh @@ -19,8 +19,8 @@ fi TARGET=$(rustc -vV | sed -n 's|host: ||p') BUILD_EXTRA="" -if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then - BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std" +if [ $TARGET = "x86_64-unknown-linux-gnu" ] || [ $TARGET = "aarch64-unknown-linux-gnu" ]; then + BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --artifact-dir out/examples/std" fi # CI intentionally does not use -eabihf on thumbv7em to minimize dep compile time. @@ -29,25 +29,23 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \ - --- build --release --manifest-path embassy-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 thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt \ --- 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 \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7a-none-eabi --features arch-cortex-ar,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabi --features arch-cortex-ar,executor-thread \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target armv7r-none-eabihf --features arch-cortex-ar,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32 \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread \ - --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features arch-riscv32,executor-thread,integrated-timers \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \ - --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \ + --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,mock-driver \ + --- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target thumbv6m-none-eabi \ + --- build --release --manifest-path embassy-time-queue-utils/Cargo.toml --target thumbv6m-none-eabi --features generic-queue-8 \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ @@ -58,6 +56,8 @@ 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-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt633s,defmt,unstable-pac,time,time-driver-rtc \ + --- build --release --manifest-path embassy-imxrt/Cargo.toml --target thumbv8m.main-none-eabihf --features mimxrt685s,defmt,unstable-pac,time,time-driver-rtc \ --- 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 \ @@ -70,6 +70,8 @@ cargo batch \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-s,gpiote,time,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-app-ns,gpiote,time,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340-net,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf54l15-app-s,gpiote,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf54l15-app-ns,gpiote,time,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,time,time-driver-rtc1 \ @@ -88,16 +90,18 @@ cargo batch \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio,rp2040 \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,defmt,rp235xa \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,log,rp235xa \ - --- 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,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \ - --- 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,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time \ - --- 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-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,rp235xa,binary-info \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt,exti \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,defmt \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,single-bank,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 \ @@ -131,7 +135,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f730i8,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h735zg,defmt,exti,time-driver-any,time \ - --- 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 stm32h755zi-cm7,defmt,exti,time-driver-any,time,split-pc2,split-pc3 \ --- 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 stm32h7b3ai,defmt,exti,time-driver-tim1,time \ @@ -153,10 +157,10 @@ cargo batch \ --- 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,low-power,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,dual-bank,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 thumbv7em-none-eabi --features stm32g474pe,dual-bank,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 \ @@ -165,11 +169,26 @@ cargo batch \ --- 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 thumbv8m.main-none-eabihf --features stm32wba50ke,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba55ug,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5f9zj,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5g9nj,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 embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg,defmt,exti,time-driver-any,low-power,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u031r8,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u073mb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-nxp/Cargo.toml --target thumbv8m.main-none-eabihf \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0c1104dgs20,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3507pm,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3519pz,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1306rhb,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l2228pn,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1345dgs28,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1106dgs28,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0l1228pm,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g1107ycj,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3105rhb,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g1505pt,defmt,time-driver-any \ + --- build --release --manifest-path embassy-mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g1519rhb,defmt,time-driver-any \ --- 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' \ @@ -178,68 +197,86 @@ cargo batch \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs,bluetooth' \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs,bluetooth' \ --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \ - --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040,overclock' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf5340-app-s \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \ --- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi --features embassy-rp/rp2040 \ --- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --no-default-features \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features log \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features defmt \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features usbd-hid \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-interface-count-1 \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-interface-count-8 \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-handler-count-8 \ + --- build --release --manifest-path embassy-usb/Cargo.toml --target thumbv6m-none-eabi --features max-handler-count-8 \ --- build --release --manifest-path docs/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \ - --- build --release --manifest-path examples/nrf52810/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52810 \ - --- 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/nrf9151/s/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/s \ - --- build --release --manifest-path examples/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/ns \ - --- 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/rp23/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/rp23 \ - --- 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-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/stm32f469/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f469 \ - --- 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 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/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \ - --- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \ - --- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \ - --- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \ - --- 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/stm32u0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32u0 \ - --- 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-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-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/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \ - --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns,skip-include --out-dir out/examples/boot/nrf9151 \ - --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns,skip-include --out-dir out/examples/boot/nrf9161 \ - --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \ - --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \ - --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \ - --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32h7 \ - --- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l0 \ - --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ - --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ - --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-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/nrf52810/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/nrf52810 \ + --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/nrf52840 \ + --- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf5340 \ + --- build --release --manifest-path examples/nrf54l15/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf54l15 \ + --- build --release --manifest-path examples/nrf9160/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf9160 \ + --- build --release --manifest-path examples/nrf9151/s/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf9151/s \ + --- build --release --manifest-path examples/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/nrf9151/ns \ + --- build --release --manifest-path examples/nrf51/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/nrf51 \ + --- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/rp \ + --- build --release --manifest-path examples/rp235x/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/rp235x \ + --- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32f0 \ + --- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --artifact-dir out/examples/stm32f1 \ + --- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --artifact-dir out/examples/stm32f2 \ + --- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f3 \ + --- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f334 \ + --- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f4 \ + --- build --release --manifest-path examples/stm32f469/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f469 \ + --- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32f7 \ + --- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32c0 \ + --- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32g0 \ + --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32g4 \ + --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32h5 \ + --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h7 \ + --- build --release --manifest-path examples/stm32h7b0/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h7b0 \ + --- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h735 \ + --- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h755cm4 \ + --- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h755cm7 \ + --- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32h7rs \ + --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32l0 \ + --- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --artifact-dir out/examples/stm32l1 \ + --- build --release --manifest-path examples/stm32l4/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32l4 \ + --- build --release --manifest-path examples/stm32l432/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32l432 \ + --- build --release --manifest-path examples/stm32l5/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32l5 \ + --- build --release --manifest-path examples/stm32u0/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/stm32u0 \ + --- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32u5 \ + --- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wb \ + --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/stm32wba \ + --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --artifact-dir out/examples/stm32wl \ + --- build --release --manifest-path examples/lpc55s69/Cargo.toml --target thumbv8m.main-none-eabihf --artifact-dir out/examples/lpc55s69 \ + --- build --release --manifest-path examples/mspm0g3507/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3507 \ + --- build --release --manifest-path examples/mspm0g3519/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0g3519 \ + --- build --release --manifest-path examples/mspm0l1306/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l1306 \ + --- build --release --manifest-path examples/mspm0l2228/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0l2228 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --artifact-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 --artifact-dir out/examples/boot/nrf9160 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --artifact-dir out/examples/boot/nrf9120 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns,skip-include --artifact-dir out/examples/boot/nrf9151 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns,skip-include --artifact-dir out/examples/boot/nrf9161 \ + --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --artifact-dir out/examples/boot/rp \ + --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32f3 \ + --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32f7 \ + --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32h7 \ + --- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32l0 \ + --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32l1 \ + --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32l4 \ + --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabi --features skip-include --artifact-dir out/examples/boot/stm32wl \ + --- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --artifact-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/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \ @@ -248,58 +285,92 @@ cargo batch \ --- 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/stm32l496zg \ --- 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/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg,verify \ --- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h743zi \ - --- 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 \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446re --out-dir out/tests/stm32f446re \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features 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 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 \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r5zi --out-dir out/tests/stm32l4r5zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze --out-dir out/tests/stm32l552ze \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f767zi --out-dir out/tests/stm32f767zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --out-dir out/tests/stm32f207zg \ - --- 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 thumbv7em-none-eabi --features stm32h7s3l8 --out-dir out/tests/stm32h7s3l8 \ - --- 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/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc --out-dir out/tests/stm32u083rc \ - --- 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 thumbv6m-none-eabi --features nrf51422 --out-dir out/tests/nrf51422-dk \ - --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832 --out-dir out/tests/nrf52832-dk \ - --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833 --out-dir out/tests/nrf52833-dk \ - --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840 --out-dir out/tests/nrf52840-dk \ - --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340 --out-dir out/tests/nrf5340-dk \ - --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160 --out-dir out/tests/nrf9160-dk \ + --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --artifact-dir out/examples/wasm \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --artifact-dir out/tests/stm32f103c8 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --artifact-dir out/tests/stm32f429zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f446re --artifact-dir out/tests/stm32f446re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --artifact-dir out/tests/stm32g491re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --artifact-dir out/tests/stm32g071rb \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --artifact-dir out/tests/stm32c031c6 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --artifact-dir out/tests/stm32h755zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi --artifact-dir out/tests/stm32h753zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --artifact-dir out/tests/stm32h7a3zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --artifact-dir out/tests/stm32wb55rg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h563zi --artifact-dir out/tests/stm32h563zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u585ai --artifact-dir out/tests/stm32u585ai \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5a5zj --artifact-dir out/tests/stm32u5a5zj \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba52cg --artifact-dir out/tests/stm32wba52cg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --artifact-dir out/tests/stm32l073rz \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --artifact-dir out/tests/stm32l152re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --artifact-dir out/tests/stm32l4a6zg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r5zi --artifact-dir out/tests/stm32l4r5zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze --artifact-dir out/tests/stm32l552ze \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f767zi --artifact-dir out/tests/stm32f767zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f207zg --artifact-dir out/tests/stm32f207zg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --artifact-dir out/tests/stm32f303ze \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --artifact-dir out/tests/stm32l496zg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55jc --artifact-dir out/tests/stm32wl55jc \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7s3l8 --artifact-dir out/tests/stm32h7s3l8 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f091rc --artifact-dir out/tests/stm32f091rc \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h503rb --artifact-dir out/tests/stm32h503rb \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32u083rc --artifact-dir out/tests/stm32u083rc \ + --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --features rp2040 --artifact-dir out/tests/rpi-pico \ + --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv8m.main-none-eabihf --features rp235xb --artifact-dir out/tests/pimoroni-pico-plus-2 \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51422 --artifact-dir out/tests/nrf51422-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52832 --artifact-dir out/tests/nrf52832-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52833 --artifact-dir out/tests/nrf52833-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840 --artifact-dir out/tests/nrf52840-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf5340 --artifact-dir out/tests/nrf5340-dk \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nrf9160 --artifact-dir out/tests/nrf9160-dk \ + --- build --release --manifest-path tests/mspm0/Cargo.toml --target thumbv6m-none-eabi --features mspm0g3507 --artifact-dir out/tests/mspm0g3507 \ --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ $BUILD_EXTRA +# MSPM0C1104 must be built seperately since cargo batch does not consider env vars set in `.cargo/config.toml`. +# Since the target has 1KB of ram, we need to limit defmt's buffer size. +DEFMT_RTT_BUFFER_SIZE="72" cargo batch \ + --- build --release --manifest-path examples/mspm0c1104/Cargo.toml --target thumbv6m-none-eabi --artifact-dir out/examples/mspm0c1104 \ + +# temporarily disabled, these boards are dead. +rm -rf out/tests/stm32f103c8 +rm -rf out/tests/nrf52840-dk +rm -rf out/tests/nrf52833-dk + +# disabled because these boards are not on the shelf +rm -rf out/tests/mspm0g3507 + rm out/tests/stm32wb55rg/wpan_mac rm out/tests/stm32wb55rg/wpan_ble # unstable, I think it's running out of RAM? rm out/tests/stm32f207zg/eth +# temporarily disabled, flaky. +rm out/tests/stm32f207zg/usart_rx_ringbuffered +rm out/tests/stm32l152re/usart_rx_ringbuffered + # doesn't work, gives "noise error", no idea why. usart_dma does pass. rm out/tests/stm32u5a5zj/usart -# flaky, perhaps bad wire -rm out/tests/stm32l152re/usart_rx_ringbuffered +# probe-rs error: "multi-core ram flash start not implemented yet" +# As of 2025-02-17 these tests work when run from flash +rm out/tests/pimoroni-pico-plus-2/multicore +rm out/tests/pimoroni-pico-plus-2/gpio_multicore +rm out/tests/pimoroni-pico-plus-2/spinlock_mutex_multicore +# Doesn't work when run from ram on the 2350 +rm out/tests/pimoroni-pico-plus-2/flash +# This test passes locally but fails on the HIL, no idea why +rm out/tests/pimoroni-pico-plus-2/i2c +# The pico2 plus doesn't have the adcs hooked up like the picoW does. +rm out/tests/pimoroni-pico-plus-2/adc +# temporarily disabled +rm out/tests/pimoroni-pico-plus-2/pwm + +# temporarily disabled, bad hardware connection. +rm -f out/tests/rpi-pico/* if [[ -z "${TELEPROBE_TOKEN-}" ]]; then echo No teleprobe token found, skipping running HIL tests diff --git a/cyw43-pio/CHANGELOG.md b/cyw43-pio/CHANGELOG.md index 913f3515a..4d56973df 100644 --- a/cyw43-pio/CHANGELOG.md +++ b/cyw43-pio/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Update embassy-rp to 0.4.0 + +## 0.3.0 - 2025-01-05 + +- Update embassy-time to 0.4.0 +- Update cyw43 to 0.3.0 +- Update embassy-rp to 0.3.0 + ## 0.2.0 - 2024-08-05 - Update to cyw43 0.2.0 diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index 4e21c255f..93a2e7089 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cyw43-pio" -version = "0.2.0" +version = "0.4.0" edition = "2021" description = "RP2040 PIO SPI implementation for cyw43" keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] @@ -9,18 +9,11 @@ license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/cyw43-pio" -[features] -# If disabled, SPI runs at 31.25MHz -# If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet. -overclock = [] - [dependencies] -cyw43 = { version = "0.2.0", path = "../cyw43" } -embassy-rp = { version = "0.2.0", path = "../embassy-rp" } -pio-proc = "0.2" -pio = "0.2.1" +cyw43 = { version = "0.3.0", path = "../cyw43" } +embassy-rp = { version = "0.4.0", path = "../embassy-rp" } fixed = "1.23.1" -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/" diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 40cf63a17..b0be19358 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -8,86 +8,106 @@ use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::dma::Channel; 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 embassy_rp::pio::program::pio_asm; +use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::Peri; +use fixed::types::extra::U8; use fixed::FixedU32; -use pio_proc::pio_asm; /// SPI comms driven by PIO. -pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA> { +pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA: Channel> { cs: Output<'d>, sm: StateMachine<'d, PIO, SM>, irq: Irq<'d, PIO, 0>, - dma: PeripheralRef<'d, DMA>, + dma: Peri<'d, DMA>, wrap_target: u8, } +/// The default clock divider that works for Pico 1 and 2 W. As well as the RM2 on rp2040 devices. +/// same speed as pico-sdk, 62.5Mhz +/// This is actually the fastest we can go without overclocking. +/// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. +/// However, the PIO uses a fractional divider, which works by introducing jitter when +/// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz +/// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles +/// violate the maximum from the data sheet. +pub const DEFAULT_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0200); + +/// The overclock clock divider for the Pico 1 W. Does not work on any known RM2 devices. +/// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to +/// data sheet, but seems to work fine. +pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0100); + +/// The clock divider for the RM2 module. Found to be needed for the Pimoroni Pico Plus 2 W, +/// Pico Plus 2 Non w with the RM2 breakout module, and the Pico 2 with the RM2 breakout module. +pub const RM2_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0300); + impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> where DMA: Channel, PIO: Instance, { /// Create a new instance of PioSpi. - pub fn new( + pub fn new( common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, + clock_divider: FixedU32, irq: Irq<'d, PIO, 0>, cs: Output<'d>, - dio: DIO, - clk: CLK, - dma: impl Peripheral

+ 'd, - ) -> Self - where - DIO: PioPin, - CLK: PioPin, - { - #[cfg(feature = "overclock")] - let program = pio_asm!( - ".side_set 1" + dio: Peri<'d, impl PioPin>, + clk: Peri<'d, impl PioPin>, + dma: Peri<'d, DMA>, + ) -> Self { + let loaded_program = if clock_divider < DEFAULT_CLOCK_DIVIDER { + let overclock_program = pio_asm!( + ".side_set 1" - ".wrap_target" - // write out x-1 bits - "lp:" - "out pins, 1 side 0" - "jmp x-- lp side 1" - // switch directions - "set pindirs, 0 side 0" - "nop side 1" // necessary for clkdiv=1. - "nop side 0" - // read in y-1 bits - "lp2:" - "in pins, 1 side 1" - "jmp y-- lp2 side 0" + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 1" // necessary for clkdiv=1. + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" - // wait for event and irq host - "wait 1 pin 0 side 0" - "irq 0 side 0" + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" - ".wrap" - ); - #[cfg(not(feature = "overclock"))] - let program = pio_asm!( - ".side_set 1" + ".wrap" + ); + common.load_program(&overclock_program.program) + } else { + let default_program = pio_asm!( + ".side_set 1" - ".wrap_target" - // write out x-1 bits - "lp:" - "out pins, 1 side 0" - "jmp x-- lp side 1" - // switch directions - "set pindirs, 0 side 0" - "nop side 0" - // read in y-1 bits - "lp2:" - "in pins, 1 side 1" - "jmp y-- lp2 side 0" + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" - // wait for event and irq host - "wait 1 pin 0 side 0" - "irq 0 side 0" + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" - ".wrap" - ); + ".wrap" + ); + common.load_program(&default_program.program) + }; let mut pin_io: embassy_rp::pio::Pin = common.make_pio_pin(dio); pin_io.set_pull(Pull::None); @@ -101,7 +121,6 @@ where pin_clk.set_slew_rate(SlewRate::Fast); let mut cfg = Config::default(); - let loaded_program = common.load_program(&program.program); cfg.use_program(&loaded_program, &[&pin_clk]); cfg.set_out_pins(&[&pin_io]); cfg.set_in_pins(&[&pin_io]); @@ -112,25 +131,7 @@ where cfg.shift_in.direction = ShiftDirection::Left; cfg.shift_in.auto_fill = true; //cfg.shift_in.threshold = 32; - - #[cfg(feature = "overclock")] - { - // 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to - // data sheet, but seems to work fine. - cfg.clock_divider = FixedU32::from_bits(0x0100); - } - - #[cfg(not(feature = "overclock"))] - { - // same speed as pico-sdk, 62.5Mhz - // This is actually the fastest we can go without overclocking. - // According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. - // However, the PIO uses a fractional divider, which works by introducing jitter when - // the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz - // so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles - // violate the maximum from the data sheet. - cfg.clock_divider = FixedU32::from_bits(0x0200); - } + cfg.clock_divider = clock_divider; sm.set_config(&cfg); @@ -141,7 +142,7 @@ where cs, sm, irq, - dma: dma.into_ref(), + dma: dma, wrap_target: loaded_program.wrap.target, } } @@ -156,20 +157,20 @@ where defmt::trace!("write={} read={}", write_bits, read_bits); unsafe { - instr::set_x(&mut self.sm, write_bits as u32); - instr::set_y(&mut self.sm, read_bits as u32); - instr::set_pindir(&mut self.sm, 0b1); - instr::exec_jmp(&mut self.sm, self.wrap_target); + self.sm.set_x(write_bits as u32); + self.sm.set_y(read_bits as u32); + self.sm.set_pindir(0b1); + self.sm.exec_jmp(self.wrap_target); } self.sm.set_enable(true); - self.sm.tx().dma_push(self.dma.reborrow(), write).await; + self.sm.tx().dma_push(self.dma.reborrow(), write, false).await; let mut status = 0; self.sm .rx() - .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false) .await; status } @@ -187,22 +188,25 @@ where defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len()); unsafe { - instr::set_y(&mut self.sm, read_bits as u32); - instr::set_x(&mut self.sm, write_bits as u32); - instr::set_pindir(&mut self.sm, 0b1); - instr::exec_jmp(&mut self.sm, self.wrap_target); + self.sm.set_y(read_bits as u32); + self.sm.set_x(write_bits as u32); + self.sm.set_pindir(0b1); + self.sm.exec_jmp(self.wrap_target); } // self.cs.set_low(); self.sm.set_enable(true); - self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await; - self.sm.rx().dma_pull(self.dma.reborrow(), read).await; + self.sm + .tx() + .dma_push(self.dma.reborrow(), slice::from_ref(&cmd), false) + .await; + self.sm.rx().dma_pull(self.dma.reborrow(), read, false).await; let mut status = 0; self.sm .rx() - .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false) .await; #[cfg(feature = "defmt")] diff --git a/cyw43/CHANGELOG.md b/cyw43/CHANGELOG.md index 1859f7317..40a638388 100644 --- a/cyw43/CHANGELOG.md +++ b/cyw43/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.3.0 - 2025-01-05 + +- Update `embassy-time` to 0.4.0 +- Add Bluetooth support. +- Add WPA3 support. +- Expand wifi security configuration options. + ## 0.2.0 - 2024-08-05 - Update to new versions of embassy-{time,sync} diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index 9b469c338..c52a653bd 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cyw43" -version = "0.2.0" +version = "0.3.0" edition = "2021" description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W." keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] @@ -18,12 +18,12 @@ bluetooth = ["dep:bt-hci", "dep:embedded-io-async"] firmware-logs = [] [dependencies] -embassy-time = { version = "0.3.2", path = "../embassy-time"} -embassy-sync = { version = "0.6.0", path = "../embassy-sync"} +embassy-time = { version = "0.4.0", path = "../embassy-time"} +embassy-sync = { version = "0.7.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"} embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.17", optional = true } cortex-m = "0.7.6" @@ -36,7 +36,7 @@ heapless = "0.8.0" # Bluetooth deps embedded-io-async = { version = "0.6.0", optional = true } -bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3", optional = true, default-features = false } +bt-hci = { version = "0.3.0", optional = true } [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" diff --git a/cyw43/README.md b/cyw43/README.md index 5b4a5d789..9e9f7a34c 100644 --- a/cyw43/README.md +++ b/cyw43/README.md @@ -1,27 +1,36 @@ # cyw43 -Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). +Rust driver for the CYW43439 wifi+bluetooth chip. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). -## Current status +Works on the following boards: + +- Raspberry Pi Pico W (RP2040) +- Raspberry Pi Pico 2 W (RP2350A) +- Pimoroni Pico Plus 2 W (RP2350B) +- Any board with Raspberry Pi RM2 radio module. +- Any board with the CYW43439 chip, and possibly others if the protocol is similar enough. + +## Features Working: -- Station mode (joining an AP). -- AP mode (creating an AP) -- Scanning -- Sending and receiving Ethernet frames. -- Using the default MAC address. -- [`embassy-net`](https://embassy.dev) integration. -- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. -- Using IRQ for device events -- GPIO support (for LED on the Pico W) +- WiFi support + - Station mode (joining an AP). + - AP mode (creating an AP) + - Scanning + - Sending and receiving Ethernet frames. + - Using the default MAC address. + - [`embassy-net`](https://embassy.dev) integration. + - RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. + - Using IRQ for device events, no busy polling. + - GPIO support (for LED on the Pico W). +- Bluetooth support + - Bluetooth Classic + LE HCI commands. + - Concurrent operation with WiFi. + - Implements the [bt-hci](https://crates.io/crates/bt-hci) controller traits. + - Works with the [TrouBLE](https://github.com/embassy-rs/trouble) bluetooth LE stack. Check its repo for examples using `cyw43`. -TODO: - -- Setting a custom MAC address. -- Bus sleep (for power consumption optimization) - -## Running the examples +## Running the WiFi examples - Install `probe-rs` following the instructions at . - `cd examples/rp` diff --git a/cyw43/src/consts.rs b/cyw43/src/consts.rs index b6e22e61d..c3f0dbfd8 100644 --- a/cyw43/src/consts.rs +++ b/cyw43/src/consts.rs @@ -113,17 +113,6 @@ pub(crate) const IRQ_F1_INTR: u16 = 0x2000; pub(crate) const IRQ_F2_INTR: u16 = 0x4000; pub(crate) const IRQ_F3_INTR: u16 = 0x8000; -pub(crate) const IOCTL_CMD_UP: u32 = 2; -pub(crate) const IOCTL_CMD_DOWN: u32 = 3; -pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; -pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; -pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52; -pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; -pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; -pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; -pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; -pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; - pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; @@ -376,3 +365,306 @@ impl core::fmt::Display for FormatInterrupt { core::fmt::Debug::fmt(self, f) } } + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub(crate) enum Ioctl { + GetMagic = 0, + GetVersion = 1, + Up = 2, + Down = 3, + GetLoop = 4, + SetLoop = 5, + Dump = 6, + GetMsglevel = 7, + SetMsglevel = 8, + GetPromisc = 9, + SetPromisc = 10, + GetRate = 12, + GetInstance = 14, + GetInfra = 19, + SetInfra = 20, + GetAuth = 21, + SetAuth = 22, + GetBssid = 23, + SetBssid = 24, + GetSsid = 25, + SetSsid = 26, + Restart = 27, + GetChannel = 29, + SetChannel = 30, + GetSrl = 31, + SetSrl = 32, + GetLrl = 33, + SetLrl = 34, + GetPlcphdr = 35, + SetPlcphdr = 36, + GetRadio = 37, + SetRadio = 38, + GetPhytype = 39, + DumpRate = 40, + SetRateParams = 41, + GetKey = 44, + SetKey = 45, + GetRegulatory = 46, + SetRegulatory = 47, + GetPassiveScan = 48, + SetPassiveScan = 49, + Scan = 50, + ScanResults = 51, + Disassoc = 52, + Reassoc = 53, + GetRoamTrigger = 54, + SetRoamTrigger = 55, + GetRoamDelta = 56, + SetRoamDelta = 57, + GetRoamScanPeriod = 58, + SetRoamScanPeriod = 59, + Evm = 60, + GetTxant = 61, + SetTxant = 62, + GetAntdiv = 63, + SetAntdiv = 64, + GetClosed = 67, + SetClosed = 68, + GetMaclist = 69, + SetMaclist = 70, + GetRateset = 71, + SetRateset = 72, + Longtrain = 74, + GetBcnprd = 75, + SetBcnprd = 76, + GetDtimprd = 77, + SetDtimprd = 78, + GetSrom = 79, + SetSrom = 80, + GetWepRestrict = 81, + SetWepRestrict = 82, + GetCountry = 83, + SetCountry = 84, + GetPm = 85, + SetPm = 86, + GetWake = 87, + SetWake = 88, + GetForcelink = 90, + SetForcelink = 91, + FreqAccuracy = 92, + CarrierSuppress = 93, + GetPhyreg = 94, + SetPhyreg = 95, + GetRadioreg = 96, + SetRadioreg = 97, + GetRevinfo = 98, + GetUcantdiv = 99, + SetUcantdiv = 100, + RReg = 101, + WReg = 102, + GetMacmode = 105, + SetMacmode = 106, + GetMonitor = 107, + SetMonitor = 108, + GetGmode = 109, + SetGmode = 110, + GetLegacyErp = 111, + SetLegacyErp = 112, + GetRxAnt = 113, + GetCurrRateset = 114, + GetScansuppress = 115, + SetScansuppress = 116, + GetAp = 117, + SetAp = 118, + GetEapRestrict = 119, + SetEapRestrict = 120, + ScbAuthorize = 121, + ScbDeauthorize = 122, + GetWdslist = 123, + SetWdslist = 124, + GetAtim = 125, + SetAtim = 126, + GetRssi = 127, + GetPhyantdiv = 128, + SetPhyantdiv = 129, + ApRxOnly = 130, + GetTxPathPwr = 131, + SetTxPathPwr = 132, + GetWsec = 133, + SetWsec = 134, + GetPhyNoise = 135, + GetBssInfo = 136, + GetPktcnts = 137, + GetLazywds = 138, + SetLazywds = 139, + GetBandlist = 140, + GetBand = 141, + SetBand = 142, + ScbDeauthenticate = 143, + GetShortslot = 144, + GetShortslotOverride = 145, + SetShortslotOverride = 146, + GetShortslotRestrict = 147, + SetShortslotRestrict = 148, + GetGmodeProtection = 149, + GetGmodeProtectionOverride = 150, + SetGmodeProtectionOverride = 151, + Upgrade = 152, + GetIgnoreBcns = 155, + SetIgnoreBcns = 156, + GetScbTimeout = 157, + SetScbTimeout = 158, + GetAssoclist = 159, + GetClk = 160, + SetClk = 161, + GetUp = 162, + Out = 163, + GetWpaAuth = 164, + SetWpaAuth = 165, + GetUcflags = 166, + SetUcflags = 167, + GetPwridx = 168, + SetPwridx = 169, + GetTssi = 170, + GetSupRatesetOverride = 171, + SetSupRatesetOverride = 172, + GetProtectionControl = 178, + SetProtectionControl = 179, + GetPhylist = 180, + EncryptStrength = 181, + DecryptStatus = 182, + GetKeySeq = 183, + GetScanChannelTime = 184, + SetScanChannelTime = 185, + GetScanUnassocTime = 186, + SetScanUnassocTime = 187, + GetScanHomeTime = 188, + SetScanHomeTime = 189, + GetScanNprobes = 190, + SetScanNprobes = 191, + GetPrbRespTimeout = 192, + SetPrbRespTimeout = 193, + GetAtten = 194, + SetAtten = 195, + GetShmem = 196, + SetShmem = 197, + SetWsecTest = 200, + ScbDeauthenticateForReason = 201, + TkipCountermeasures = 202, + GetPiomode = 203, + SetPiomode = 204, + SetAssocPrefer = 205, + GetAssocPrefer = 206, + SetRoamPrefer = 207, + GetRoamPrefer = 208, + SetLed = 209, + GetLed = 210, + GetInterferenceMode = 211, + SetInterferenceMode = 212, + GetChannelQa = 213, + StartChannelQa = 214, + GetChannelSel = 215, + StartChannelSel = 216, + GetValidChannels = 217, + GetFakefrag = 218, + SetFakefrag = 219, + GetPwroutPercentage = 220, + SetPwroutPercentage = 221, + SetBadFramePreempt = 222, + GetBadFramePreempt = 223, + SetLeapList = 224, + GetLeapList = 225, + GetCwmin = 226, + SetCwmin = 227, + GetCwmax = 228, + SetCwmax = 229, + GetWet = 230, + SetWet = 231, + GetPub = 232, + GetKeyPrimary = 235, + SetKeyPrimary = 236, + GetAciArgs = 238, + SetAciArgs = 239, + UnsetCallback = 240, + SetCallback = 241, + GetRadar = 242, + SetRadar = 243, + SetSpectManagment = 244, + GetSpectManagment = 245, + WdsGetRemoteHwaddr = 246, + WdsGetWpaSup = 247, + SetCsScanTimer = 248, + GetCsScanTimer = 249, + MeasureRequest = 250, + Init = 251, + SendQuiet = 252, + Keepalive = 253, + SendPwrConstraint = 254, + UpgradeStatus = 255, + CurrentPwr = 256, + GetScanPassiveTime = 257, + SetScanPassiveTime = 258, + LegacyLinkBehavior = 259, + GetChannelsInCountry = 260, + GetCountryList = 261, + GetVar = 262, + SetVar = 263, + NvramGet = 264, + NvramSet = 265, + NvramDump = 266, + Reboot = 267, + SetWsecPmk = 268, + GetAuthMode = 269, + SetAuthMode = 270, + GetWakeentry = 271, + SetWakeentry = 272, + NdconfigItem = 273, + Nvotpw = 274, + Otpw = 275, + IovBlockGet = 276, + IovModulesGet = 277, + SoftReset = 278, + GetAllowMode = 279, + SetAllowMode = 280, + GetDesiredBssid = 281, + SetDesiredBssid = 282, + DisassocMyap = 283, + GetNbands = 284, + GetBandstates = 285, + GetWlcBssInfo = 286, + GetAssocInfo = 287, + GetOidPhy = 288, + SetOidPhy = 289, + SetAssocTime = 290, + GetDesiredSsid = 291, + GetChanspec = 292, + GetAssocState = 293, + SetPhyState = 294, + GetScanPending = 295, + GetScanreqPending = 296, + GetPrevRoamReason = 297, + SetPrevRoamReason = 298, + GetBandstatesPi = 299, + GetPhyState = 300, + GetBssWpaRsn = 301, + GetBssWpa2Rsn = 302, + GetBssBcnTs = 303, + GetIntDisassoc = 304, + SetNumPeers = 305, + GetNumBss = 306, + GetWsecPmk = 318, + GetRandomBytes = 319, +} + +pub(crate) const WSEC_TKIP: u32 = 0x02; +pub(crate) const WSEC_AES: u32 = 0x04; + +pub(crate) const AUTH_OPEN: u32 = 0x00; +pub(crate) const AUTH_SAE: u32 = 0x03; + +pub(crate) const MFP_NONE: u32 = 0; +pub(crate) const MFP_CAPABLE: u32 = 1; +pub(crate) const MFP_REQUIRED: u32 = 2; + +pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000; +pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004; +pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080; +pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000; diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 22c52bd96..f77b487e2 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -35,16 +35,22 @@ pub struct Control<'a> { ioctl_state: &'a IoctlState, } -#[derive(Copy, Clone)] +/// WiFi scan type. +#[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ScanType { + /// Active scan: the station actively transmits probes that make APs respond. + /// Faster, but uses more power. Active, + /// Passive scan: the station doesn't transmit any probes, just listens for beacons. + /// Slower, but uses less power. Passive, } /// Scan options. -#[derive(Clone)] +#[derive(Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] pub struct ScanOptions { /// SSID to scan for. pub ssid: Option>, @@ -74,6 +80,79 @@ impl Default for ScanOptions { } } +/// Authentication type, used in [`JoinOptions::auth`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum JoinAuth { + /// Open network + Open, + /// WPA only + Wpa, + /// WPA2 only + Wpa2, + /// WPA3 only + Wpa3, + /// WPA2 + WPA3 + Wpa2Wpa3, +} + +/// Options for [`Control::join`]. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct JoinOptions<'a> { + /// Authentication type. Default `Wpa2Wpa3`. + pub auth: JoinAuth, + /// Enable TKIP encryption. Default false. + pub cipher_tkip: bool, + /// Enable AES encryption. Default true. + pub cipher_aes: bool, + /// Passphrase. Default empty. + pub passphrase: &'a [u8], + /// If false, `passphrase` is the human-readable passphrase string. + /// If true, `passphrase` is the result of applying the PBKDF2 hash to the + /// passphrase string. This makes it possible to avoid storing unhashed passwords. + /// + /// This is not compatible with WPA3. + /// Default false. + pub passphrase_is_prehashed: bool, +} + +impl<'a> JoinOptions<'a> { + /// Create a new `JoinOptions` for joining open networks. + pub fn new_open() -> Self { + Self { + auth: JoinAuth::Open, + cipher_tkip: false, + cipher_aes: false, + passphrase: &[], + passphrase_is_prehashed: false, + } + } + + /// Create a new `JoinOptions` for joining encrypted networks. + /// + /// Defaults to supporting WPA2+WPA3 with AES only, you may edit + /// the returned options to change this. + pub fn new(passphrase: &'a [u8]) -> Self { + let mut this = Self::default(); + this.passphrase = passphrase; + this + } +} + +impl<'a> Default for JoinOptions<'a> { + fn default() -> Self { + Self { + auth: JoinAuth::Wpa2Wpa3, + cipher_tkip: false, + cipher_aes: true, + passphrase: &[], + passphrase_is_prehashed: false, + } + } +} + impl<'a> Control<'a> { pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { Self { @@ -109,7 +188,7 @@ impl<'a> Control<'a> { buf[0..8].copy_from_slice(b"clmload\x00"); buf[8..20].copy_from_slice(&header.to_bytes()); buf[20..][..chunk.len()].copy_from_slice(&chunk); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()]) .await; } @@ -145,7 +224,7 @@ impl<'a> Control<'a> { Timer::after_millis(100).await; // Set antenna to chip antenna - self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await; self.set_iovar_u32("bus:txglom", 0).await; Timer::after_millis(100).await; @@ -183,8 +262,8 @@ impl<'a> Control<'a> { Timer::after_millis(100).await; - self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto - self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any Timer::after_millis(100).await; @@ -195,12 +274,12 @@ impl<'a> Control<'a> { /// Set the WiFi interface up. async fn up(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await; } /// Set the interface down. async fn down(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await; } /// Set power management mode. @@ -213,17 +292,74 @@ impl<'a> Control<'a> { self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; } - self.ioctl_set_u32(86, 0, mode_num).await; + self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await; } /// Join an unprotected network with the provided ssid. - pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { + pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> { self.set_iovar_u32("ampdu_ba_wsize", 8).await; - self.ioctl_set_u32(134, 0, 0).await; // wsec = open - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) + if options.auth == JoinAuth::Open { + self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await; + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await; + } else { + let mut wsec = 0; + if options.cipher_aes { + wsec |= WSEC_AES; + } + if options.cipher_tkip { + wsec |= WSEC_TKIP; + } + self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await; + + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after_millis(100).await; + + let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth { + JoinAuth::Open => unreachable!(), + JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK), + JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK), + JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK), + JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK), + }; + + if wpa12 { + let mut flags = 0; + if !options.passphrase_is_prehashed { + flags |= 1; + } + let mut pfi = PassphraseInfo { + len: options.passphrase.len() as _, + flags, + passphrase: [0; 64], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) + .await; + } + + if wpa3 { + let mut pfi = SaePassphraseInfo { + len: options.passphrase.len() as _, + passphrase: [0; 128], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.set_iovar("sae_password", &pfi.to_bytes()).await; + } + + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await; + self.set_iovar_u32("mfp", mfp).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await; + } let mut i = SsidInfo { len: ssid.len() as _, @@ -234,69 +370,13 @@ impl<'a> Control<'a> { self.wait_for_join(i).await } - /// Join a protected network with the provided ssid and [`PassphraseInfo`]. - async fn join_wpa2_passphrase_info(&mut self, ssid: &str, passphrase_info: &PassphraseInfo) -> Result<(), Error> { - self.set_iovar_u32("ampdu_ba_wsize", 8).await; - - self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; - self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; - self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; - - Timer::after_millis(100).await; - - self.ioctl( - IoctlType::Set, - IOCTL_CMD_SET_PASSPHRASE, - 0, - &mut passphrase_info.to_bytes(), - ) - .await; // WLC_SET_WSEC_PMK - - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) - self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth - - let mut i = SsidInfo { - len: ssid.len() as _, - ssid: [0; 32], - }; - i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - - self.wait_for_join(i).await - } - - /// Join a protected network with the provided ssid and passphrase. - pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { - let mut pfi = PassphraseInfo { - len: passphrase.len() as _, - flags: 1, - passphrase: [0; 64], - }; - pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); - self.join_wpa2_passphrase_info(ssid, &pfi).await - } - - /// Join a protected network with the provided ssid and precomputed PSK. - pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> { - let mut pfi = PassphraseInfo { - len: psk.len() as _, - flags: 0, - passphrase: [0; 64], - }; - pfi.passphrase[..psk.len()].copy_from_slice(psk); - self.join_wpa2_passphrase_info(ssid, &pfi).await - } - async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); let mut subscriber = self.events.queue.subscriber().unwrap(); // the actual join operation starts here // we make sure to enable events before so we don't miss any - // set_ssid - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) - .await; + self.ioctl(IoctlType::Set, Ioctl::SetSsid, 0, &mut i.to_bytes()).await; // to complete the join, we wait for a SET_SSID event // we also save the AUTH status for the user, it may be interesting @@ -357,7 +437,7 @@ impl<'a> Control<'a> { self.up().await; // Turn on AP mode - self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await; // Set SSID let mut i = SsidInfoWithIndex { @@ -371,7 +451,7 @@ impl<'a> Control<'a> { self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; // Set channel number - self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; + self.ioctl_set_u32(Ioctl::SetChannel, 0, channel as u32).await; // Set security self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; @@ -388,7 +468,7 @@ impl<'a> Control<'a> { passphrase: [0; 64], }; pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) .await; } @@ -405,7 +485,7 @@ impl<'a> Control<'a> { self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN // Turn off AP mode - self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await; // Temporarily set wifi down self.down().await; @@ -451,7 +531,7 @@ impl<'a> Control<'a> { } /// Retrieve the list of configured multicast hardware addresses. - pub async fn list_mulistcast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize { + pub async fn list_multicast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize { let mut buf = [0; 64]; self.get_iovar("mcast_list", &mut buf).await; @@ -484,11 +564,11 @@ impl<'a> Control<'a> { } async fn set_iovar(&mut self, name: &str, val: &[u8]) { - self.set_iovar_v::<64>(name, val).await + self.set_iovar_v::<196>(name, val).await } async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { - debug!("set {} = {:02x}", name, Bytes(val)); + debug!("iovar set {} = {:02x}", name, Bytes(val)); let mut buf = [0; BUFSIZE]; buf[..name.len()].copy_from_slice(name.as_bytes()); @@ -496,13 +576,13 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) + self.ioctl_inner(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len]) .await; } // TODO this is not really working, it always returns all zeros. async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { - debug!("get {}", name); + debug!("iovar get {}", name); let mut buf = [0; 64]; buf[..name.len()].copy_from_slice(name.as_bytes()); @@ -510,7 +590,7 @@ impl<'a> Control<'a> { let total_len = max(name.len() + 1, res.len()); let res_len = self - .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) + .ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len]) .await; let out_len = min(res.len(), res_len); @@ -518,12 +598,20 @@ impl<'a> Control<'a> { out_len } - async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { + async fn ioctl_set_u32(&mut self, cmd: Ioctl, iface: u32, val: u32) { let mut buf = val.to_le_bytes(); self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; } - async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + if kind == IoctlType::Set { + debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf)); + } + let n = self.ioctl_inner(kind, cmd, iface, buf).await; + n + } + + async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { struct CancelOnDrop<'a>(&'a IoctlState); impl CancelOnDrop<'_> { @@ -615,7 +703,7 @@ impl<'a> Control<'a> { } /// Leave the wifi, with which we are currently associated. pub async fn leave(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await; info!("Disassociated") } diff --git a/cyw43/src/events.rs b/cyw43/src/events.rs index 44bfa98e9..2a78b38c3 100644 --- a/cyw43/src/events.rs +++ b/cyw43/src/events.rs @@ -296,10 +296,10 @@ pub struct Events { } impl Events { - pub fn new() -> Self { + pub const fn new() -> Self { Self { queue: EventQueue::new(), - mask: SharedEventMask::default(), + mask: SharedEventMask::new(), } } } @@ -333,7 +333,6 @@ impl Message { } } -#[derive(Default)] struct EventMask { mask: [u32; Self::WORD_COUNT], } @@ -341,6 +340,12 @@ struct EventMask { impl EventMask { const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize; + const fn new() -> Self { + Self { + mask: [0; Self::WORD_COUNT], + } + } + fn enable(&mut self, event: Event) { let n = event as u32; let word = n / u32::BITS; @@ -366,13 +371,17 @@ impl EventMask { } } -#[derive(Default)] - pub struct SharedEventMask { mask: RefCell, } impl SharedEventMask { + pub const fn new() -> Self { + Self { + mask: RefCell::new(EventMask::new()), + } + } + pub fn enable(&self, events: &[Event]) { let mut mask = self.mask.borrow_mut(); for event in events { @@ -398,3 +407,9 @@ impl SharedEventMask { mask.is_enabled(event) } } + +impl Default for SharedEventMask { + fn default() -> Self { + Self::new() + } +} diff --git a/cyw43/src/ioctl.rs b/cyw43/src/ioctl.rs index 61524c274..35135e296 100644 --- a/cyw43/src/ioctl.rs +++ b/cyw43/src/ioctl.rs @@ -1,12 +1,13 @@ use core::cell::{Cell, RefCell}; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::task::{Poll, Waker}; use embassy_sync::waitqueue::WakerRegistration; +use crate::consts::Ioctl; use crate::fmt::Bytes; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum IoctlType { Get = 0, Set = 2, @@ -16,7 +17,7 @@ pub enum IoctlType { pub struct PendingIoctl { pub buf: *mut [u8], pub kind: IoctlType, - pub cmd: u32, + pub cmd: Ioctl, pub iface: u32, } @@ -32,8 +33,8 @@ struct Wakers { runner: WakerRegistration, } -impl Default for Wakers { - fn default() -> Self { +impl Wakers { + const fn new() -> Self { Self { control: WakerRegistration::new(), runner: WakerRegistration::new(), @@ -47,10 +48,10 @@ pub struct IoctlState { } impl IoctlState { - pub fn new() -> Self { + pub const fn new() -> Self { Self { state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), - wakers: Default::default(), + wakers: RefCell::new(Wakers::new()), } } @@ -70,7 +71,7 @@ impl IoctlState { self.wakers.borrow_mut().runner.register(waker); } - pub async fn wait_complete(&self) -> usize { + pub fn wait_complete(&self) -> impl Future + '_ { poll_fn(|cx| { if let IoctlStateInner::Done { resp_len } = self.state.get() { Poll::Ready(resp_len) @@ -79,29 +80,25 @@ impl IoctlState { Poll::Pending } }) - .await } - pub async fn wait_pending(&self) -> PendingIoctl { - let pending = poll_fn(|cx| { + pub fn wait_pending(&self) -> impl Future + '_ { + poll_fn(|cx| { if let IoctlStateInner::Pending(pending) = self.state.get() { + self.state.set(IoctlStateInner::Sent { buf: pending.buf }); Poll::Ready(pending) } else { self.register_runner(cx.waker()); Poll::Pending } }) - .await; - - self.state.set(IoctlStateInner::Sent { buf: pending.buf }); - pending } pub fn cancel_ioctl(&self) { self.state.set(IoctlStateInner::Done { resp_len: 0 }); } - pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { self.state .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); self.wake_runner(); diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs index efeb3f313..16b436e66 100644 --- a/cyw43/src/lib.rs +++ b/cyw43/src/lib.rs @@ -9,7 +9,8 @@ pub(crate) mod fmt; #[cfg(feature = "bluetooth")] -mod bluetooth; +/// Bluetooth module. +pub mod bluetooth; mod bus; mod consts; mod control; @@ -28,7 +29,9 @@ use ioctl::IoctlState; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; -pub use crate::control::{AddMulticastAddressError, Control, Error as ControlError, ScanOptions, Scanner}; +pub use crate::control::{ + AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, ScanType, Scanner, +}; pub use crate::runner::Runner; pub use crate::structs::BssInfo; @@ -121,7 +124,7 @@ struct NetState { impl State { /// Create new driver state holder. - pub fn new() -> Self { + pub const fn new() -> Self { Self { ioctl_state: IoctlState::new(), net: NetState { diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs index 959718341..77910b281 100644 --- a/cyw43/src/runner.rs +++ b/cyw43/src/runner.rs @@ -560,7 +560,7 @@ 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], buf: &mut [u32; 512]) { + async fn send_ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) { let buf8 = slice8_mut(buf); let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); @@ -582,7 +582,7 @@ where }; let cdc_header = CdcHeader { - cmd: cmd, + cmd: cmd as u32, len: data.len() as _, flags: kind as u16 | (iface as u16) << 12, id: self.ioctl_id, diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs index ae7ef6038..81ae6a98d 100644 --- a/cyw43/src/structs.rs +++ b/cyw43/src/structs.rs @@ -394,6 +394,15 @@ pub struct PassphraseInfo { } impl_bytes!(PassphraseInfo); +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SaePassphraseInfo { + pub len: u16, + pub passphrase: [u8; 128], +} +impl_bytes!(SaePassphraseInfo); + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] diff --git a/docs/examples/basic/.cargo/config.toml b/docs/examples/basic/.cargo/config.toml index 8ca28df39..17616a054 100644 --- a/docs/examples/basic/.cargo/config.toml +++ b/docs/examples/basic/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace nRF82840_xxAA with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip nRF52840_xxAA" +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" [build] target = "thumbv7em-none-eabi" diff --git a/docs/examples/basic/Cargo.toml b/docs/examples/basic/Cargo.toml index 5d391adf3..f5cf2b231 100644 --- a/docs/examples/basic/Cargo.toml +++ b/docs/examples/basic/Cargo.toml @@ -6,13 +6,13 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt"] } -embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } +embassy-executor = { version = "0.7.0", path = "../../../embassy-executor", features = ["defmt", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../embassy-time", features = ["defmt"] } +embassy-nrf = { version = "0.3.1", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } -defmt = "0.3" -defmt-rtt = "0.3" +defmt = "1.0.1" +defmt-rtt = "1.0.0" 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"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } diff --git a/docs/examples/layer-by-layer/.cargo/config.toml b/docs/examples/layer-by-layer/.cargo/config.toml index 3012f05dc..f30d9e446 100644 --- a/docs/examples/layer-by-layer/.cargo/config.toml +++ b/docs/examples/layer-by-layer/.cargo/config.toml @@ -1,5 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-run --chip STM32L475VG" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L475VG" rustflags = [ "-C", "link-arg=--nmagic", diff --git a/docs/examples/layer-by-layer/Cargo.toml b/docs/examples/layer-by-layer/Cargo.toml index 0f233eae5..01666ec6e 100644 --- a/docs/examples/layer-by-layer/Cargo.toml +++ b/docs/examples/layer-by-layer/Cargo.toml @@ -3,14 +3,9 @@ resolver = "2" members = [ "blinky-pac", "blinky-hal", - "blinky-irq", "blinky-async", ] -[patch.crates-io] -embassy-executor = { path = "../../../embassy-executor" } -embassy-stm32 = { path = "../../../embassy-stm32" } - [profile.release] codegen-units = 1 debug = 2 diff --git a/docs/examples/layer-by-layer/blinky-async/Cargo.toml b/docs/examples/layer-by-layer/blinky-async/Cargo.toml index 7f8d8af3e..2a6741b33 100644 --- a/docs/examples/layer-by-layer/blinky-async/Cargo.toml +++ b/docs/examples/layer-by-layer/blinky-async/Cargo.toml @@ -5,11 +5,11 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -cortex-m = "0.7" +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" -embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] } -embassy-executor = { version = "0.6.0", features = ["arch-cortex-m", "executor-thread"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x", "exti"] } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } -defmt = "0.3.0" -defmt-rtt = "0.3.0" -panic-probe = { version = "0.3.0", features = ["print-defmt"] } +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } diff --git a/docs/examples/layer-by-layer/blinky-hal/Cargo.toml b/docs/examples/layer-by-layer/blinky-hal/Cargo.toml index c15de2db2..9a261f804 100644 --- a/docs/examples/layer-by-layer/blinky-hal/Cargo.toml +++ b/docs/examples/layer-by-layer/blinky-hal/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -cortex-m = "0.7" cortex-m-rt = "0.7" -embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] } +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x"] } -defmt = "0.3.0" -defmt-rtt = "0.3.0" -panic-probe = { version = "0.3.0", features = ["print-defmt"] } +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } diff --git a/docs/examples/layer-by-layer/blinky-irq/Cargo.toml b/docs/examples/layer-by-layer/blinky-irq/Cargo.toml index 9733658b6..c3ec9ad1a 100644 --- a/docs/examples/layer-by-layer/blinky-irq/Cargo.toml +++ b/docs/examples/layer-by-layer/blinky-irq/Cargo.toml @@ -1,3 +1,5 @@ +[workspace] + [package] name = "blinky-irq" version = "0.1.0" @@ -5,10 +7,10 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -cortex-m = "0.7" +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = { version = "0.7" } -embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "unstable-pac"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "memory-x", "unstable-pac"] } -defmt = "0.3.0" -defmt-rtt = "0.3.0" -panic-probe = { version = "0.3.0", features = ["print-defmt"] } +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } diff --git a/docs/examples/layer-by-layer/blinky-pac/Cargo.toml b/docs/examples/layer-by-layer/blinky-pac/Cargo.toml index f872b94cb..4e7e2f2ff 100644 --- a/docs/examples/layer-by-layer/blinky-pac/Cargo.toml +++ b/docs/examples/layer-by-layer/blinky-pac/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" license = "MIT OR Apache-2.0" [dependencies] -cortex-m = "0.7" +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m-rt = "0.7" -stm32-metapac = { version = "1", features = ["stm32l475vg", "memory-x"] } +stm32-metapac = { version = "16", features = ["stm32l475vg"] } -defmt = "0.3.0" -defmt-rtt = "0.3.0" -panic-probe = { version = "0.3.0", features = ["print-defmt"] } +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } diff --git a/docs/examples/layer-by-layer/blinky-pac/src/main.rs b/docs/examples/layer-by-layer/blinky-pac/src/main.rs index 990d46cb6..cfbd91306 100644 --- a/docs/examples/layer-by-layer/blinky-pac/src/main.rs +++ b/docs/examples/layer-by-layer/blinky-pac/src/main.rs @@ -8,46 +8,38 @@ use {defmt_rtt as _, panic_probe as _, stm32_metapac as pac}; fn main() -> ! { // Enable GPIO clock let rcc = pac::RCC; - unsafe { - rcc.ahb2enr().modify(|w| { - w.set_gpioben(true); - w.set_gpiocen(true); - }); + rcc.ahb2enr().modify(|w| { + w.set_gpioben(true); + w.set_gpiocen(true); + }); - rcc.ahb2rstr().modify(|w| { - w.set_gpiobrst(true); - w.set_gpiocrst(true); - w.set_gpiobrst(false); - w.set_gpiocrst(false); - }); - } + rcc.ahb2rstr().modify(|w| { + w.set_gpiobrst(true); + w.set_gpiocrst(true); + w.set_gpiobrst(false); + w.set_gpiocrst(false); + }); // Setup button let gpioc = pac::GPIOC; const BUTTON_PIN: usize = 13; - unsafe { - gpioc.pupdr().modify(|w| w.set_pupdr(BUTTON_PIN, vals::Pupdr::PULLUP)); - gpioc.otyper().modify(|w| w.set_ot(BUTTON_PIN, vals::Ot::PUSHPULL)); - gpioc.moder().modify(|w| w.set_moder(BUTTON_PIN, vals::Moder::INPUT)); - } + gpioc.pupdr().modify(|w| w.set_pupdr(BUTTON_PIN, vals::Pupdr::PULL_UP)); + gpioc.otyper().modify(|w| w.set_ot(BUTTON_PIN, vals::Ot::PUSH_PULL)); + gpioc.moder().modify(|w| w.set_moder(BUTTON_PIN, vals::Moder::INPUT)); // Setup LED let gpiob = pac::GPIOB; const LED_PIN: usize = 14; - unsafe { - gpiob.pupdr().modify(|w| w.set_pupdr(LED_PIN, vals::Pupdr::FLOATING)); - gpiob.otyper().modify(|w| w.set_ot(LED_PIN, vals::Ot::PUSHPULL)); - gpiob.moder().modify(|w| w.set_moder(LED_PIN, vals::Moder::OUTPUT)); - } + gpiob.pupdr().modify(|w| w.set_pupdr(LED_PIN, vals::Pupdr::FLOATING)); + gpiob.otyper().modify(|w| w.set_ot(LED_PIN, vals::Ot::PUSH_PULL)); + gpiob.moder().modify(|w| w.set_moder(LED_PIN, vals::Moder::OUTPUT)); // Main loop loop { - unsafe { - if gpioc.idr().read().idr(BUTTON_PIN) == vals::Idr::LOW { - gpiob.bsrr().write(|w| w.set_bs(LED_PIN, true)); - } else { - gpiob.bsrr().write(|w| w.set_br(LED_PIN, true)); - } + if gpioc.idr().read().idr(BUTTON_PIN) == vals::Idr::LOW { + gpiob.bsrr().write(|w| w.set_bs(LED_PIN, true)); + } else { + gpiob.bsrr().write(|w| w.set_br(LED_PIN, true)); } } } diff --git a/docs/examples/layer-by-layer/memory.x b/docs/examples/layer-by-layer/memory.x new file mode 100644 index 000000000..69f5b28a1 --- /dev/null +++ b/docs/examples/layer-by-layer/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 2048K /* BANK_1 */ + RAM : ORIGIN = 0x20000000, LENGTH = 640K /* SRAM */ +} diff --git a/docs/pages/best_practices.adoc b/docs/pages/best_practices.adoc index bfcedec06..eabaa9eb9 100644 --- a/docs/pages/best_practices.adoc +++ b/docs/pages/best_practices.adoc @@ -35,7 +35,7 @@ After the processing, another 1024 byte buffer will be placed on the stack to be Pass the data by reference and not by value on both, the way in and the way out. For example, you could return a slice of the input buffer as the output. -Requiring the lifetime of the input slice and the output slice to be the same, the memory safetly of this procedure will be enforced by the compiler. +Requiring the lifetime of the input slice and the output slice to be the same, the memory safety of this procedure will be enforced by the compiler. [,rust] ---- diff --git a/docs/pages/bootloader.adoc b/docs/pages/bootloader.adoc index 3b0cdb182..b0f0331aa 100644 --- a/docs/pages/bootloader.adoc +++ b/docs/pages/bootloader.adoc @@ -2,6 +2,13 @@ `embassy-boot` a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. +The update method used is referred to as an A/B partition update scheme. + +With a general-purpose OS, A/B partition update is accomplished by directly booting either the A or B partition depending on the update state. +To accomplish the same goal in a way that is portable across all microcontrollers, `embassy-boot` swaps data page by page (in both directions) between the DFU and the Active partition when a firmware update is triggered. + +Because the original Active application is moved into the DFU partition during this update, the operation can be reversed if the update is interrupted or the new firmware does not flag that it booted successfully. + +See the design section for more details on how this is implemented. + The bootloader can be used either as a library or be flashed directly if you are happy with the default configuration and capabilities. By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. @@ -19,6 +26,8 @@ The bootloader supports In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work. +STM32L0x1 devices require the `flash-erase-zero` feature to be enabled. + == Design image::bootloader_flash.png[Bootloader flash layout] @@ -86,8 +95,7 @@ Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware [source, bash] ---- -shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt -cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt +shasum -a 512 -b $FIRMWARE_DIR/myfirmware | head -c128 | xxd -p -r > $SECRETS_DIR/message.txt signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed diff --git a/docs/pages/embassy_in_the_wild.adoc b/docs/pages/embassy_in_the_wild.adoc index 76b1169bd..620794c31 100644 --- a/docs/pages/embassy_in_the_wild.adoc +++ b/docs/pages/embassy_in_the_wild.adoc @@ -2,6 +2,10 @@ Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]! +* link:https://github.com/1-rafael-1/simple-robot[A simple tracked robot based on Raspberry Pi Pico 2] +** A hobbyist project building a tracked robot with basic autonomous and manual drive mode. +* link:https://github.com/1-rafael-1/pi-pico-alarmclock-rust[A Raspberry Pi Pico W Alarmclock] +** A hobbyist project building an alarm clock around a Pi Pico W complete with code, components list and enclosure design files. * link:https://github.com/haobogu/rmk/[RMK: A feature-rich Rust keyboard firmware] ** RMK has built-in layer support, wireless(BLE) support, real-time key editing support using vial, and more! ** Targets STM32, RP2040, nRF52 and ESP32 MCUs diff --git a/docs/pages/faq.adoc b/docs/pages/faq.adoc index 4ab04e2a1..b21be9a30 100644 --- a/docs/pages/faq.adoc +++ b/docs/pages/faq.adoc @@ -4,7 +4,7 @@ These are a list of unsorted, commonly asked questions and answers. Please feel free to add items to link:https://github.com/embassy-rs/embassy/edit/main/docs/pages/faq.adoc[this page], especially if someone in the chat answered a question for you! -== How to deploy to RP2040 without a debugging probe. +== How to deploy to RP2040 or RP235x without a debugging probe. Install link:https://github.com/JoNil/elf2uf2-rs[elf2uf2-rs] for converting the generated elf binary into a uf2 file. @@ -92,17 +92,9 @@ If you see linker error like this: >>> referenced by driver.rs:127 (src/driver.rs:127) >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::now::hefb1f99d6e069842) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib - rust-lld: error: undefined symbol: _embassy_time_allocate_alarm - >>> referenced by driver.rs:134 (src/driver.rs:134) - >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::allocate_alarm::hf5145b6bd46706b2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib - - rust-lld: error: undefined symbol: _embassy_time_set_alarm_callback - >>> referenced by driver.rs:139 (src/driver.rs:139) - >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm_callback::h24f92388d96eafd2) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib - - rust-lld: error: undefined symbol: _embassy_time_set_alarm + rust-lld: error: undefined symbol: _embassy_time_schedule_wake >>> referenced by driver.rs:144 (src/driver.rs:144) - >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::set_alarm::h530a5b1f444a6d5b) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib + >>> embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::schedule_wake::h530a5b1f444a6d5b) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib ---- You probably need to enable a time driver for your HAL (not in `embassy-time`!). For example with `embassy-stm32`, you might need to enable `time-driver-any`: @@ -125,6 +117,20 @@ If you are in the early project setup phase and not using anything from the HAL, use embassy_stm32 as _; ---- +Another common error you may experience is: + +[source,text] +---- + = note: rust-lld: error: undefined symbol: __pender + >>> referenced by mod.rs:373 (src/raw/mod.rs:373) + >>> embassy_executor-e78174e249bca7f4.embassy_executor.1e9d60fc90940543-cgu.0.rcgu.o:(embassy_executor::raw::Pender::pend::h0f19b6e01762e4cd) in archive [...]libembassy_executor-e78174e249bca7f4.rlib +---- + +There are two possible causes to this error: + +* You are using `embassy-executor` withuout enabling one of the architecture-specific features, but you are using a HAL that does not bring its own executors. For example, for Cortex-M (like the RP2040), you need to enable the `arch-cortex-m` feature of `embassy-executor`. +* You are not using `embassy-executor`. In this case, you need to enable the one of the `generic-queue-X` features of `embassy-time`. + == 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 @@ -140,9 +146,9 @@ Example: [source,toml] ---- [patch.crates-io] -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" } +embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" } +embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" } +# embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" } ---- Note that the git revision should match any other embassy patches or git dependencies that you are using! @@ -158,35 +164,11 @@ Note that the git revision should match any other embassy patches or git depende * Set the following keys in the `[unstable]` section of your `.cargo/config.toml` ** `build-std = ["core"]` ** `build-std-features = ["panic_immediate_abort"]` -* Enable feature `embassy-time/generic-queue`, disable feature `embassy-executor/integrated-timers` * When using `InterruptExecutor`: ** disable `executor-thread` - ** make `main`` spawn everything, then enable link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html#method.set_sleeponexit[SCB.SLEEPONEXIT] and `loop { cortex_m::asm::wfi() }` + ** make `main` spawn everything, then enable link:https://docs.rs/cortex-m/latest/cortex_m/peripheral/struct.SCB.html#method.set_sleeponexit[SCB.SLEEPONEXIT] and `loop { cortex_m::asm::wfi() }` ** *Note:* If you need 2 priority levels, using 2 interrupt executors is better than 1 thread executor + 1 interrupt executor. -== How do I set up the task arenas on stable? - -When you aren't using the `nightly` feature of `embassy-executor`, the executor uses a bump allocator, which may require configuration. - -Something like this error will occur at **compile time** if the task arena is *too large* for the target's RAM: - -[source,plain] ----- -rust-lld: error: section '.bss' will not fit in region 'RAM': overflowed by _ bytes -rust-lld: error: section '.uninit' will not fit in region 'RAM': overflowed by _ bytes ----- - -And this message will appear at **runtime** if the task arena is *too small* for the tasks running: - -[source,plain] ----- -ERROR panicked at 'embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/' ----- - -NOTE: If all tasks are spawned at startup, this panic will occur immediately. - -Check out link:https://docs.embassy.dev/embassy-executor/git/cortex-m/index.html#task-arena[Task Arena Documentation] for more details. - == 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]. @@ -230,7 +212,7 @@ _stack_start = ORIGIN(RAM) + LENGTH(RAM); Please refer to the STM32 documentation for the specific values suitable for your board and setup. The STM32 Cube examples often contain a linker script `.ld` file. Look for the `MEMORY` section and try to determine the FLASH and RAM sizes and section start. -If you find a case where the memory.x is wrong, please report it on [this Github issue](https://github.com/embassy-rs/stm32-data/issues/301) so other users are not caught by surprise. +If you find a case where the memory.x is wrong, please report it on link:https://github.com/embassy-rs/stm32-data/issues/301[this Github issue] so other users are not caught by surprise. == The USB examples are not working on my board, is there anything else I need to configure? @@ -286,7 +268,7 @@ General steps: 1. Find out which memory region BDMA has access to. You can get this information from the bus matrix and the memory mapping table in the STM32 datasheet. 2. Add the memory region to `memory.x`, you can modify the generated one from https://github.com/embassy-rs/stm32-data-generated/tree/main/data/chips. 3. You might need to modify `build.rs` to make cargo pick up the modified `memory.x`. -4. In your code, access the defined memory region using `#[link_section = ".xxx"]` +4. In your code, access the defined memory region using `#[unsafe(link_section = ".xxx")]` 5. Copy data to that region before using BDMA. See link:https://github.com/embassy-rs/embassy/blob/main/examples/stm32h7/src/bin/spi_bdma.rs[SMT32H7 SPI BDMA example] for more details. @@ -361,4 +343,73 @@ Practically, there's not a LOT of difference either way - so go with what makes == splitting peripherals resources between tasks -There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example] \ No newline at end of file +There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example] + +== My code/driver works in debug mode, but not release mode (or with LTO) + +Issues like these while implementing drivers often fall into one of the following general causes, which are a good list of common errors to check for: + +1. Some kind of race condition - the faster code means you miss an interrupt or something +2. Some kind of UB, if you have unsafe code, or something like DMA with fences missing +3. Some kind of hardware errata, or some hardware misconfiguration like wrong clock speeds +4. Some issue with an interrupt handler, either enabling, disabling, or re-enabling of interrupts when necessary +5. Some kind of async issue, like not registering wakers fully before checking flags, or not registering or pending wakers at the right time + +== How can I prevent the thread-mode executor from going to sleep? == + +In some cases you might want to prevent the thread-mode executor from going to sleep, for example when doing so would result in current spikes that reduce analog performance. +As a workaround, you can spawn a task that yields in a loop, preventing the executor from going to sleep. Note that this may increase power consumption. + +[source,rust] +---- +#[embassy_executor::task] +async fn idle() { + loop { embassy_futures::yield_now().await; } +} +---- + +== Why is my bootloader restarting in loop? + +== Troubleshooting Bootloader Restart Loops + +If your bootloader restarts in a loop, there could be multiple reasons. Here are some things to check: + +=== Validate the `memory.x` File +The bootloader performs critical checks when creating partitions using the addresses defined in `memory.x`. Ensure the following assertions hold true: + +[source,rust] +---- +const { + core::assert!(Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % DFU::WRITE_SIZE as u32 == 0); + core::assert!(Self::PAGE_SIZE % DFU::ERASE_SIZE as u32 == 0); +} + +// Ensure enough progress pages to store copy progress +assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); +assert!(aligned_buf.len() >= STATE::WRITE_SIZE); +assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); +assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); +---- + +If any of these assertions fail, the bootloader will likely restart in a loop. This failure might not log any messages (e.g., when using `defmt`). Confirm that your `memory.x` file and flash memory align with these requirements. + +=== Handling Panic Logging +Some panic errors might log messages, but certain microcontrollers reset before the message is fully printed. To ensure panic messages are logged, add a delay using no-operation (NOP) instructions before the reset: + +[source,rust] +---- +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + for _ in 0..10_000_000 { + cortex_m::asm::nop(); + } + cortex_m::asm::udf(); +} +---- + +=== Feed the watchdog + + +Some `embassy-boot` implementations (like `embassy-boot-nrf` and `embassy-boot-rp`) rely on a watchdog timer to detect application failure. The bootloader will restart if your application code does not properly feed the watchdog timer. Make sure to feed it correctly. diff --git a/docs/pages/getting_started.adoc b/docs/pages/getting_started.adoc index 017409018..d1f65a885 100644 --- a/docs/pages/getting_started.adoc +++ b/docs/pages/getting_started.adoc @@ -66,7 +66,7 @@ If everything worked correctly, you should see a blinking LED on your board, and [source] ---- Finished dev [unoptimized + debuginfo] target(s) in 1m 56s - Running `probe-run --chip STM32F407VGTx target/thumbv7em-none-eabi/debug/blinky` + Running `probe-rs run --chip STM32F407VGTx target/thumbv7em-none-eabi/debug/blinky` (HOST) INFO flashing program (71.36 KiB) (HOST) INFO success! ──────────────────────────────────────────────────────────────────────────────── @@ -86,7 +86,7 @@ NOTE: How does the `+cargo run+` command know how to connect to our board and pr === It didn’t work! -If you hare having issues when running `+cargo run --release+`, please check the following: +If you are 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 diff --git a/docs/pages/hal.adoc b/docs/pages/hal.adoc index 14b85e1f1..3bbe94e02 100644 --- a/docs/pages/hal.adoc +++ b/docs/pages/hal.adoc @@ -4,7 +4,7 @@ Embassy provides HALs for several microcontroller families: * `embassy-nrf` for the nRF microcontrollers from Nordic Semiconductor * `embassy-stm32` for STM32 microcontrollers from ST Microelectronics -* `embassy-rp` for the Raspberry Pi RP2040 microcontrollers +* `embassy-rp` for the Raspberry Pi RP2040 and RP235x microcontrollers These HALs implement async/await functionality for most peripherals while also implementing the async traits in `embedded-hal` and `embedded-hal-async`. You can also use these HALs with another executor. @@ -12,3 +12,7 @@ async traits in `embedded-hal` and `embedded-hal-async`. You can also use these For the ESP32 series, there is an link:https://github.com/esp-rs/esp-hal[esp-hal] which you can use. For the WCH 32-bit RISC-V series, there is an link:https://github.com/ch32-rs/ch32-hal[ch32-hal], which you can use. + +For the Microchip PolarFire SoC, there is link:https://github.com/AlexCharlton/mpfs-hal[mpfs-hal]. + +For the Puya Semiconductor PY32 series, there is link:https://github.com/py32-rs/py32-hal[py32-hal]. \ No newline at end of file diff --git a/docs/pages/imxrt.adoc b/docs/pages/imxrt.adoc new file mode 100644 index 000000000..87867e1e0 --- /dev/null +++ b/docs/pages/imxrt.adoc @@ -0,0 +1,16 @@ += Embassy iMXRT HAL + +The link: link:https://github.com/embassy-rs/embassy/tree/main/embassy-imxrt[Embassy iMXRT HAL] is based on the following PACs (Peripheral Access Crate): + +* link:https://github.com/OpenDevicePartnership/mimxrt685s-pac[mimxrt685s-pac] +* link:https://github.com/OpenDevicePartnership/mimxrt633s-pac[mimxrt633s-pac] + +== Peripherals + +The following peripherals have a HAL implementation at present + +* CRC +* DMA +* GPIO +* RNG +* UART diff --git a/docs/pages/layer_by_layer.adoc b/docs/pages/layer_by_layer.adoc index 7852d27b7..7dba11b5e 100644 --- a/docs/pages/layer_by_layer.adoc +++ b/docs/pages/layer_by_layer.adoc @@ -76,7 +76,7 @@ The async version looks very similar to the HAL version, apart from a few minor * The peripheral initialization is done by the main macro, and is handed to the main task. * Before checking the button state, the application is awaiting a transition in the pin state (low -> high or high -> low). -When `button.await_for_any_edge().await` is called, the executor will pause the main task and put the microcontroller in sleep mode, unless there are other tasks that can run. Internally, the Embassy HAL has configured the interrupt handler for the button (in `ExtiInput`), so that whenever an interrupt is raised, the task awaiting the button will be woken up. +When `button.wait_for_any_edge().await` is called, the executor will pause the main task and put the microcontroller in sleep mode, unless there are other tasks that can run. Internally, the Embassy HAL has configured the interrupt handler for the button (in `ExtiInput`), so that whenever an interrupt is raised, the task awaiting the button will be woken up. The minimal overhead of the executor and the ability to run multiple tasks "concurrently" combined with the enormous simplification of the application, makes `async` a great fit for embedded. diff --git a/docs/pages/new_project.adoc b/docs/pages/new_project.adoc index 821bcbd27..cd943b4f6 100644 --- a/docs/pages/new_project.adoc +++ b/docs/pages/new_project.adoc @@ -73,22 +73,34 @@ Now that cargo knows what target to compile for (and probe-rs knows what chip to Looking in `examples/stm32g4/Cargo.toml`, we can see that the examples require a number of embassy crates. For blinky, we’ll only need three of them: `embassy-stm32`, `embassy-executor` and `embassy-time`. -At the time of writing, the latest version of embassy isn‘t available on crates.io, so we need to install it straight from the git repository. The recommended way of doing so is as follows: + +At the time of writing, embassy is already published to crates.io. Therefore, dependencies can easily added via Cargo.toml. + +[source,toml] +---- +[dependencies] +embassy-stm32 = { version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"] } +embassy-executor = { version = "0.6.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +---- + +Prior, embassy needed to be installed straight from the git repository. Installing from git is still useful, if you want to checkout a specic revision of an embassy crate which is not yet published. +The recommended way of doing so is as follows: * Copy the required `embassy-*` lines from the example `Cargo.toml` * Make any necessary changes to `features`, e.g. requiring the `stm32g474re` feature of `embassy-stm32` * Remove the `path = ""` keys in the `embassy-*` entries * Create a `[patch.crates-io]` section, with entries for each embassy crate we need. These should all contain identical values: a link to the git repository, and a reference to the commit we’re checking out. Assuming you want the latest commit, you can find it by running `git ls-remote https://github.com/embassy-rs/embassy.git HEAD` -NOTE: When using this method, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. Hopefully this will no longer be necessary once embassy is released on crates.io! +NOTE: When using this method, it’s necessary that the `version` keys in `[dependencies]` match up with the versions defined in each crate’s `Cargo.toml` in the specificed `rev` under `[patch.crates.io]`. This means that when updating, you have to a pick a new revision, change everything in `[patch.crates.io]` to match it, and then correct any versions under `[dependencies]` which have changed. -At the time of writing, this method produces the following results: +An example Cargo.toml file might look as follows: [source,toml] ---- [dependencies] embassy-stm32 = {version = "0.1.0", features = ["defmt", "time-driver-any", "stm32g474re", "memory-x", "unstable-pac", "exti"]} -embassy-executor = { version = "0.3.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-executor = { version = "0.3.3", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.2", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } [patch.crates-io] @@ -97,7 +109,7 @@ embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "7703f embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "7703f47c1ecac029f603033b7977d9a2becef48c" } ---- -There are a few other dependencies we need to build the project, but fortunately they’re much simpler to install. Copy their lines from the example `Cargo.toml` to the the `[dependencies]` section in the new `Cargo.toml`: +There are a few other dependencies we need to build the project, but fortunately they’re much simpler to install. Copy their lines from the example `Cargo.toml` to the `[dependencies]` section in the new `Cargo.toml`: [source,toml] ---- @@ -138,7 +150,7 @@ stm32g474-example # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2023-11-01" +channel = "1.85" components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] targets = ["thumbv7em-none-eabi"] ---- diff --git a/docs/pages/overview.adoc b/docs/pages/overview.adoc index 7d59d5521..acd757795 100644 --- a/docs/pages/overview.adoc +++ b/docs/pages/overview.adoc @@ -6,7 +6,7 @@ Embassy is a project to make async/await a first-class option for embedded devel When handling I/O, software must call functions that block program execution until the I/O operation completes. When running inside of an OS such as Linux, such functions generally transfer control to the kernel so that another task (known as a “thread”) can be executed if available, or the CPU can be put to sleep until another task is ready. -Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted they do not transfer control back to the kernel within an allotted time. If tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread. +Because an OS cannot presume that threads will behave cooperatively, threads are relatively resource-intensive, and may be forcibly interrupted if they do not transfer control back to the kernel within an allotted time. If tasks could be presumed to behave cooperatively, or at least not maliciously, it would be possible to create tasks that appear to be almost free when compared to a traditional OS thread. In other programming languages, these lightweight tasks are known as “coroutines” or ”goroutines”. In Rust, they are implemented with async. Async-await works by transforming each async function into an object called a future. When a future blocks on I/O the future yields, and the scheduler, called an executor, can select a different future to execute. @@ -28,9 +28,12 @@ The Embassy project maintains HALs for select hardware, but you can still use HA * link:https://docs.embassy.dev/embassy-stm32/[embassy-stm32], for all STM32 microcontroller families. * link:https://docs.embassy.dev/embassy-nrf/[embassy-nrf], for the Nordic Semiconductor nRF52, nRF53, nRF91 series. -* link:https://docs.embassy.dev/embassy-rp/[embassy-rp], for the Raspberry Pi RP2040 microcontroller. +* link:https://docs.embassy.dev/embassy-rp/[embassy-rp], for the Raspberry Pi RP2040 as well as RP235x microcontroller. +* link:https://docs.embassy.dev/embassy-mspm0/[embassy-mspm0], for the Texas Instruments MSPM0 microcontrollers. * link:https://github.com/esp-rs[esp-rs], for the Espressif Systems ESP32 series of chips. * link:https://github.com/ch32-rs/ch32-hal[ch32-hal], for the WCH 32-bit RISC-V(CH32V) series of chips. +* link:https://github.com/AlexCharlton/mpfs-hal[mpfs-hal], for the Microchip PolarFire SoC. +* link:https://github.com/py32-rs/py32-hal[py32-hal], for the Puya Semiconductor PY32 series of chips. NOTE: A common question is if one can use the Embassy HALs standalone. Yes, it is possible! There are no dependency on the executor within the HALs. You can even use them without async, as they implement both the link:https://github.com/rust-embedded/embedded-hal[Embedded HAL] blocking and async traits. @@ -52,7 +55,7 @@ link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot] == What is DMA? -For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bits at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller. +For most I/O in embedded devices, the peripheral doesn't directly support the transmission of multiple bytes at once, with CAN being a notable exception. Instead, the MCU must write each byte, one at a time, and then wait until the peripheral is ready to send the next. For high I/O rates, this can pose a problem if the MCU must devote an increasing portion of its time handling each byte. The solution to this problem is to use the Direct Memory Access controller. The Direct Memory Access controller (DMA) is a controller that is present in MCUs that Embassy supports, including stm32 and nrf. The DMA allows the MCU to set up a transfer, either send or receive, and then wait for the transfer to complete. With DMA, once started, no MCU intervention is required until the transfer is complete, meaning that the MCU can perform other computation, or set up other I/O while the transfer is in progress. For high I/O rates, DMA can cut the time that the MCU spends handling I/O by over half. However, because DMA is more complex to set-up, it is less widely used in the embedded community. Embassy aims to change that by making DMA the first choice rather than the last. Using Embassy, there's no additional tuning required once I/O rates increase because your application is already set-up to handle them. @@ -74,6 +77,11 @@ include::embassy_in_the_wild.adoc[leveloffset = 2] For more reading material on async Rust and Embassy: -* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy] +* link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparison of FreeRTOS and Embassy] * link:https://dev.to/apollolabsbin/series/20707[Tutorials] * link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy] + +Videos: + +* link:https://www.youtube.com/watch?v=pDd5mXBF4tY[Intro to Embassy] +* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] diff --git a/docs/pages/project_structure.adoc b/docs/pages/project_structure.adoc index 722ec8d9d..227508b97 100644 --- a/docs/pages/project_structure.adoc +++ b/docs/pages/project_structure.adoc @@ -85,9 +85,9 @@ A minimal example: [source,toml] ---- [toolchain] -channel = "nightly-2023-08-19" # <- as of writing, this is the exact rust version embassy uses +channel = "1.85" # <- as of writing, this is the exact rust version embassy uses components = [ "rust-src", "rustfmt" ] # <- optionally add "llvm-tools-preview" for some extra features like "cargo size" targets = [ - "thumbv6m-none-eabi" # <-change for your platform + "thumbv6m-none-eabi" # <- change for your platform ] ---- diff --git a/docs/pages/system.adoc b/docs/pages/system.adoc index 985f92b18..09241a8df 100644 --- a/docs/pages/system.adoc +++ b/docs/pages/system.adoc @@ -6,6 +6,7 @@ include::runtime.adoc[leveloffset = 2] include::bootloader.adoc[leveloffset = 2] include::time_keeping.adoc[leveloffset = 2] include::hal.adoc[leveloffset = 2] +include::imxrt.adoc[leveloffset = 2] include::nrf.adoc[leveloffset = 2] include::stm32.adoc[leveloffset = 2] include::sharing_peripherals.adoc[leveloffset = 2] diff --git a/embassy-boot-nrf/Cargo.toml b/embassy-boot-nrf/Cargo.toml index 27407ae92..3cfaa5a80 100644 --- a/embassy-boot-nrf/Cargo.toml +++ b/embassy-boot-nrf/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-nrf" -version = "0.3.0" +version = "0.4.0" description = "Bootloader lib for nRF chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -21,12 +21,12 @@ target = "thumbv7em-none-eabi" [lib] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.17", optional = true } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-nrf = { version = "0.2.0", path = "../embassy-nrf", default-features = false } -embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-nrf = { version = "0.3.1", path = "../embassy-nrf", default-features = false } +embassy-boot = { version = "0.4.0", path = "../embassy-boot" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" diff --git a/embassy-boot-nrf/README.md b/embassy-boot-nrf/README.md index f0d87e18c..b77cf80bd 100644 --- a/embassy-boot-nrf/README.md +++ b/embassy-boot-nrf/README.md @@ -6,6 +6,25 @@ An adaptation of `embassy-boot` for nRF. ## Features -* Load applications with or without the softdevice. -* Configure bootloader partitions based on linker script. -* Using watchdog timer to detect application failure. +- Load applications with or without the softdevice. +- Configure bootloader partitions based on linker script. +- Using watchdog timer to detect application failure. + +## Working with a SoftDevice + +When a SoftDevice is present, it handles starting the bootloader and the application as needed. + +The SoftDevice architecture supports the bootloader via a configurable base address, referred to as `BOOTLOADERADDR`, in the application flash region. This address can be specified either: + +1. At the `MBR_BOOTLOADER_ADDR` location in flash memory (defined in `nrf_mbr.h`), or +2. In the `UICR.NRFFW[0]` register. + +The `UICR.NRFFW[0]` register is used only if `MBR_BOOTLOADER_ADDR` has its default value of `0xFFFFFFFF`. This bootloader relies on the latter approach. + +In the `memory.x` linker script, there is a section `.uicr_bootloader_start_address` (origin `0x10001014`, length `0x4`) that stores the `BOOTLOADERADDR` value. +Ensure that `__bootloader_start` is set to the origin address of the bootloader partition. + +When a bootloader is present, the SoftDevice forwards interrupts to it and executes the bootloader reset handler, defined in the bootloader's vector table at `BOOTLOADERADDR`. + +Once the bootloader loads the application, the SoftDevice initiates the Application Reset Handler, defined in the application’s vector table at APP_CODE_BASE hardcoded in the SoftDevice. +The active partition's origin **must** match the `APP_CODE_BASE` value hardcoded within the SoftDevice. This value can be found in the release notes for each SoftDevice version. diff --git a/embassy-boot-nrf/src/lib.rs b/embassy-boot-nrf/src/lib.rs index e5bc870b5..f1c9da080 100644 --- a/embassy-boot-nrf/src/lib.rs +++ b/embassy-boot-nrf/src/lib.rs @@ -8,8 +8,7 @@ pub use embassy_boot::{ FirmwareUpdater, FirmwareUpdaterConfig, }; use embassy_nrf::nvmc::PAGE_SIZE; -use embassy_nrf::peripherals::WDT; -use embassy_nrf::wdt; +use embassy_nrf::{wdt, Peri}; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; /// A bootloader for nRF devices. @@ -113,7 +112,7 @@ pub struct WatchdogFlash { impl WatchdogFlash { /// Start a new watchdog with a given flash and WDT peripheral and a timeout - pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self { + pub fn start(flash: FLASH, wdt: Peri<'static, impl wdt::Instance>, config: wdt::Config) -> Self { let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { Ok(x) => x, Err(_) => { diff --git a/embassy-boot-rp/Cargo.toml b/embassy-boot-rp/Cargo.toml index bf5f34abe..afe5d6691 100644 --- a/embassy-boot-rp/Cargo.toml +++ b/embassy-boot-rp/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-rp" -version = "0.3.0" +version = "0.5.0" description = "Bootloader lib for RP2040 chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -21,13 +21,13 @@ features = ["embassy-rp/rp2040"] [lib] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4", optional = true } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-rp = { version = "0.2.0", path = "../embassy-rp", default-features = false } -embassy-boot = { version = "0.3.0", path = "../embassy-boot" } -embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-rp = { version = "0.4.0", path = "../embassy-rp", default-features = false } +embassy-boot = { version = "0.4.0", path = "../embassy-boot" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } diff --git a/embassy-boot-rp/src/lib.rs b/embassy-boot-rp/src/lib.rs index 3e1731f5e..f704380ef 100644 --- a/embassy-boot-rp/src/lib.rs +++ b/embassy-boot-rp/src/lib.rs @@ -10,11 +10,15 @@ pub use embassy_boot::{ use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; use embassy_rp::peripherals::{FLASH, WATCHDOG}; use embassy_rp::watchdog::Watchdog; +use embassy_rp::Peri; use embassy_time::Duration; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; /// A bootloader for RP2040 devices. -pub struct BootLoader; +pub struct BootLoader { + /// The reported state of the bootloader after preparing for boot + pub state: State, +} impl BootLoader { /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware @@ -36,8 +40,8 @@ impl BootLoader { ) -> Result { 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())?; - Ok(Self) + let state = boot.prepare_boot(aligned_buf.as_mut())?; + Ok(Self { state }) } /// Boots the application. @@ -65,7 +69,7 @@ pub struct WatchdogFlash<'d, const SIZE: usize> { impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { /// Start a new watchdog with a given flash and watchdog peripheral and a timeout - pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { + pub fn start(flash: Peri<'static, FLASH>, watchdog: Peri<'static, WATCHDOG>, timeout: Duration) -> Self { let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash); let mut watchdog = Watchdog::new(watchdog); watchdog.start(timeout); diff --git a/embassy-boot-stm32/Cargo.toml b/embassy-boot-stm32/Cargo.toml index e4ef9a612..11ad453b8 100644 --- a/embassy-boot-stm32/Cargo.toml +++ b/embassy-boot-stm32/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-stm32" -version = "0.2.0" +version = "0.3.0" description = "Bootloader lib for STM32 chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -21,12 +21,12 @@ target = "thumbv7em-none-eabi" [lib] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4", optional = true } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false } -embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-stm32 = { version = "0.2.0", path = "../embassy-stm32", default-features = false } +embassy-boot = { version = "0.4.0", path = "../embassy-boot" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml index 85b3695a1..f12e8e304 100644 --- a/embassy-boot/Cargo.toml +++ b/embassy-boot/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot" -version = "0.3.0" +version = "0.4.0" description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -24,12 +24,12 @@ features = ["defmt"] [lib] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } digest = "0.10" log = { version = "0.4", optional = true } -ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } -embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embedded-storage = "0.3.1" embedded-storage-async = { version = "0.4.1" } salty = { version = "0.3", optional = true } @@ -42,11 +42,12 @@ rand = "0.8" futures = { version = "0.3", features = ["executor"] } sha1 = "0.10.5" critical-section = { version = "1.1.1", features = ["std"] } -ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } +ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] } [features] ed25519-dalek = ["dep:ed25519-dalek", "_verify"] ed25519-salty = ["dep:salty", "_verify"] +flash-erase-zero = [] #Internal features _verify = [] diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs index 61d61b96e..5bffdc5ea 100644 --- a/embassy-boot/src/boot_loader.rs +++ b/embassy-boot/src/boot_loader.rs @@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; +use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -276,7 +276,7 @@ impl BootLoader BootLoader FirmwareUpdater<'d, DFU, STATE> { let mut message = [0; 64]; self.hash::(_update_len, &mut chunk_buf, &mut message).await?; - public_key.verify(&message, &signature).map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated().await; } #[cfg(feature = "ed25519-salty")] { @@ -134,10 +135,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { message, r.is_ok() ); - r.map_err(into_signature_error)? + r.map_err(into_signature_error)?; + return self.state.mark_updated().await; + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) } - - self.state.mark_updated().await } /// Verify the update in DFU with any digest. @@ -157,6 +161,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { Ok(()) } + /// Read a slice of data from the DFU storage peripheral, starting the read + /// operation at the given address offset, and reading `buf.len()` bytes. + /// + /// # Errors + /// + /// Returns an error if the arguments are not aligned or out of bounds. + pub async fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + self.dfu.read(offset, buf).await?; + Ok(()) + } + /// Mark to trigger firmware swap on next boot. #[cfg(not(feature = "_verify"))] pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { @@ -285,7 +300,8 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state().await? == State::Boot { + let state = self.get_state().await?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) @@ -299,12 +315,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { /// `mark_booted`. pub async fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned).await?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned[..STATE::WRITE_SIZE])) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index 35772a856..0fedac1ea 100644 --- a/embassy-boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -142,7 +142,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> let mut chunk_buf = [0; 2]; self.hash::(_update_len, &mut chunk_buf, &mut message)?; - public_key.verify(&message, &signature).map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated(); } #[cfg(feature = "ed25519-salty")] { @@ -169,10 +170,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> message, r.is_ok() ); - r.map_err(into_signature_error)? + r.map_err(into_signature_error)?; + return self.state.mark_updated(); + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) } - - self.state.mark_updated() } /// Verify the update in DFU with any digest. @@ -192,6 +196,17 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> Ok(()) } + /// Read a slice of data from the DFU storage peripheral, starting the read + /// operation at the given address offset, and reading `buf.len()` bytes. + /// + /// # Errors + /// + /// Returns an error if the arguments are not aligned or out of bounds. + pub fn read_dfu(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + self.dfu.read(offset, buf)?; + Ok(()) + } + /// Mark to trigger firmware swap on next boot. #[cfg(not(feature = "_verify"))] pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { @@ -320,7 +335,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { + let state = self.get_state()?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) @@ -334,14 +350,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { /// `mark_booted`. pub fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned)?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned)) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs index 4c4f4f10b..4814786bf 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_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition +/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition /// the provided flash according to symbols defined in the linkerfile. pub struct FirmwareUpdaterConfig { /// The dfu flash partition diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs index b4f03e01e..e2c4cf771 100644 --- a/embassy-boot/src/lib.rs +++ b/embassy-boot/src/lib.rs @@ -14,13 +14,18 @@ mod test_flash; // The expected value of the flash after an erase // TODO: Use the value provided by NorFlash when available +#[cfg(not(feature = "flash-erase-zero"))] pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; +#[cfg(feature = "flash-erase-zero")] +pub(crate) const STATE_ERASE_VALUE: u8 = 0x00; + pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; pub use firmware_updater::{ BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError, }; +pub(crate) const REVERT_MAGIC: u8 = 0xC0; pub(crate) const BOOT_MAGIC: u8 = 0xD0; pub(crate) const SWAP_MAGIC: u8 = 0xF0; pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; @@ -33,10 +38,30 @@ pub enum State { Boot, /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, + /// Bootloader has reverted the active partition with the dfu partition and will attempt boot. + Revert, /// Application has received a request to reboot into DFU mode to apply an update. DfuDetach, } +impl From for State +where + T: AsRef<[u8]>, +{ + fn from(magic: T) -> State { + let magic = magic.as_ref(); + if !magic.iter().any(|&b| b != SWAP_MAGIC) { + State::Swap + } else if !magic.iter().any(|&b| b != REVERT_MAGIC) { + State::Revert + } else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) { + State::DfuDetach + } else { + State::Boot + } + } +} + /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. #[repr(align(32))] pub struct AlignedBuffer(pub [u8; N]); @@ -153,6 +178,9 @@ mod tests { // Running again should cause a revert assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + // Next time we know it was reverted + assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap()); + let mut read_buf = [0; FIRMWARE_SIZE]; flash.active().read(0, &mut read_buf).unwrap(); assert_eq!(ORIGINAL, read_buf); diff --git a/embassy-embedded-hal/CHANGELOG.md b/embassy-embedded-hal/CHANGELOG.md index f8e272160..224036af4 100644 --- a/embassy-embedded-hal/CHANGELOG.md +++ b/embassy-embedded-hal/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.3.0 - 2025-01-05 + +- The `std` feature has been removed +- Updated `embassy-time` to v0.4 + ## 0.2.0 - 2024-08-05 - Add Clone derive to flash Partition in embassy-embedded-hal diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index 345dc3420..efc3173d4 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-embedded-hal" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy." @@ -15,21 +15,17 @@ categories = [ [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/" -features = ["std"] target = "x86_64-unknown-linux-gnu" -[package.metadata.docs.rs] -features = ["std"] - [features] -std = [] time = ["dep:embassy-time"] default = ["time"] [dependencies] +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ "unproven", ] } @@ -39,7 +35,7 @@ embedded-storage = "0.3.1" embedded-storage-async = { version = "0.4.1" } nb = "1.0.0" -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } [dev-dependencies] critical-section = { version = "1.1.1", features = ["std"] } diff --git a/embassy-embedded-hal/src/lib.rs b/embassy-embedded-hal/src/lib.rs index 99a2d93c7..1db4fe012 100644 --- a/embassy-embedded-hal/src/lib.rs +++ b/embassy-embedded-hal/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(async_fn_in_trait)] #![warn(missing_docs)] #![doc = include_str!("../README.md")] diff --git a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs index 30d4ecc36..78709b7d3 100644 --- a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs +++ b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs @@ -25,6 +25,7 @@ //! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128); //! ``` +use embassy_hal_internal::drop::OnDrop; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; use embedded_hal_1::digital::OutputPin; @@ -70,6 +71,14 @@ where let mut bus = self.bus.lock().await; self.cs.set_low().map_err(SpiDeviceError::Cs)?; + let cs_drop = OnDrop::new(|| { + // This drop guard deasserts CS pin if the async operation is cancelled. + // Errors are ignored in this drop handler, as there's nothing we can do about them. + // If the async operation is completed without cancellation, this handler will not + // be run, and the CS pin will be deasserted with proper error handling. + let _ = self.cs.set_high(); + }); + let op_res = 'ops: { for op in operations { let res = match op { @@ -97,6 +106,10 @@ where // On failure, it's important to still flush and deassert CS. let flush_res = bus.flush().await; + + // Now that all the async operations are done, we defuse the CS guard, + // and manually set the CS pin low (to better handle the possible errors). + cs_drop.defuse(); let cs_res = self.cs.set_high(); let op_res = op_res.map_err(SpiDeviceError::Spi)?; @@ -155,6 +168,11 @@ where bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?; self.cs.set_low().map_err(SpiDeviceError::Cs)?; + let cs_drop = OnDrop::new(|| { + // Please see comment in SpiDevice for an explanation of this drop handler. + let _ = self.cs.set_high(); + }); + let op_res = 'ops: { for op in operations { let res = match op { @@ -182,6 +200,7 @@ where // On failure, it's important to still flush and deassert CS. let flush_res = bus.flush().await; + cs_drop.defuse(); let cs_res = self.cs.set_high(); let op_res = op_res.map_err(SpiDeviceError::Spi)?; diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml index 218e820ce..5a38f2f06 100644 --- a/embassy-executor-macros/Cargo.toml +++ b/embassy-executor-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor-macros" -version = "0.5.0" +version = "0.6.2" edition = "2021" license = "MIT OR Apache-2.0" description = "macros for creating the entry point and tasks for embassy-executor" @@ -13,7 +13,7 @@ categories = [ ] [dependencies] -syn = { version = "2.0.15", features = ["full", "extra-traits"] } +syn = { version = "2.0.15", features = ["full", "visit"] } quote = "1.0.9" darling = "0.20.1" proc-macro2 = "1.0.29" diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index 5461fe04c..962ce2e75 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -1,28 +1,11 @@ #![doc = include_str!("../README.md")] extern crate proc_macro; -use darling::ast::NestedMeta; use proc_macro::TokenStream; mod macros; mod util; use macros::*; -use syn::parse::{Parse, ParseBuffer}; -use syn::punctuated::Punctuated; -use syn::Token; - -struct Args { - meta: Vec, -} - -impl Parse for Args { - fn parse(input: &ParseBuffer) -> syn::Result { - let meta = Punctuated::::parse_terminated(input)?; - Ok(Args { - meta: meta.into_iter().collect(), - }) - } -} /// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how /// many concurrent tasks can be spawned (default is 1) for the function. @@ -56,17 +39,12 @@ impl Parse for Args { /// ``` #[proc_macro_attribute] pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - - task::run(&args.meta, f).unwrap_or_else(|x| x).into() + task::run(args.into(), item.into()).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() + main::run(args.into(), item.into(), &main::ARCH_AVR).into() } /// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. @@ -89,9 +67,57 @@ pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into() +} + +/// Creates a new `executor` instance and declares an application entry point for Cortex-A/R +/// spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it +/// can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_cortex_ar(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_CORTEX_AR).into() +} + +/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning +/// the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro must provided via the `entry` argument +/// +/// ## Examples +/// Spawning a task: +/// ``` rust +/// #[embassy_executor::main(entry = "qingke_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_SPIN).into() } /// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. @@ -124,11 +150,7 @@ pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::riscv(&args.meta)) - .unwrap_or_else(|x| x) - .into() + main::run(args.into(), item.into(), &main::ARCH_RISCV).into() } /// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. @@ -151,9 +173,7 @@ pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_STD).into() } /// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. @@ -176,7 +196,29 @@ pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { /// ``` #[proc_macro_attribute] pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as Args); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into() + main::run(args.into(), item.into(), &main::ARCH_WASM).into() +} + +/// Creates a new `executor` instance and declares an application entry point for an unspecified architecture, spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro and executor type must be provided via the `entry` and `executor` arguments of the `main` macro. +/// +/// ## Examples +/// Spawning a task: +/// ``` rust +/// #[embassy_executor::main(entry = "your_hal::entry", executor = "your_hal::Executor")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_unspecified(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_UNSPECIFIED).into() } diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 26dfa2397..a0e7b3401 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -1,125 +1,128 @@ +use std::str::FromStr; + use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::TokenStream; use quote::quote; -use syn::{Expr, ReturnType, Type}; +use syn::{ReturnType, Type}; -use crate::util::ctxt::Ctxt; +use crate::util::*; -#[derive(Debug, FromMeta)] +enum Flavor { + Standard, + Wasm, +} + +pub(crate) struct Arch { + default_entry: Option<&'static str>, + flavor: Flavor, + executor_required: bool, +} + +pub static ARCH_AVR: Arch = Arch { + default_entry: Some("avr_device::entry"), + flavor: Flavor::Standard, + executor_required: false, +}; + +pub static ARCH_RISCV: Arch = Arch { + default_entry: Some("riscv_rt::entry"), + flavor: Flavor::Standard, + executor_required: false, +}; + +pub static ARCH_CORTEX_M: Arch = Arch { + default_entry: Some("cortex_m_rt::entry"), + flavor: Flavor::Standard, + executor_required: false, +}; + +pub static ARCH_CORTEX_AR: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, + executor_required: false, +}; + +pub static ARCH_SPIN: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, + executor_required: false, +}; + +pub static ARCH_STD: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, + executor_required: false, +}; + +pub static ARCH_WASM: Arch = Arch { + default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"), + flavor: Flavor::Wasm, + executor_required: false, +}; + +pub static ARCH_UNSPECIFIED: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, + executor_required: true, +}; + +#[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] entry: Option, + #[darling(default)] + executor: Option, } -pub fn avr() -> TokenStream { - quote! { - #[avr_device::entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; +pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { + let mut errors = TokenStream::new(); - 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, - Err(e) => return e.write_errors(), + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), }; - let entry = maybe_entry.unwrap_or("riscv_rt::entry".into()); - let entry = match Expr::from_string(&entry) { - Ok(expr) => expr, - Err(e) => return e.write_errors(), + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), }; - quote! { - #[#entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() } - } -} - -pub fn cortex_m() -> TokenStream { - quote! { - #[cortex_m_rt::entry] - fn main() -> ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - } -} - -pub fn wasm() -> TokenStream { - quote! { - #[wasm_bindgen::prelude::wasm_bindgen(start)] - pub fn main() -> Result<(), wasm_bindgen::JsValue> { - let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); - - executor.start(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }); - - Ok(()) - } - } -} - -pub fn std() -> TokenStream { - quote! { - 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 run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result { - #[allow(unused_variables)] - let args = Args::from_list(args).map_err(|e| e.write_errors())?; + }; let fargs = f.sig.inputs.clone(); - let ctxt = Ctxt::new(); - if f.sig.asyncness.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must be async"); + error(&mut errors, &f.sig, "main function must be async"); } if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&f.sig, "main function must not be generic"); + error(&mut errors, &f.sig, "main function must not be generic"); } if !f.sig.generics.where_clause.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); + error(&mut errors, &f.sig, "main function must not have `where` clauses"); } if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); + error(&mut errors, &f.sig, "main function must not have an ABI qualifier"); } if !f.sig.variadic.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); + error(&mut errors, &f.sig, "main function must not be variadic"); } match &f.sig.output { ReturnType::Default => {} ReturnType::Type(_, ty) => match &**ty { Type::Tuple(tuple) if tuple.elems.is_empty() => {} Type::Never(_) => {} - _ => ctxt.error_spanned_by( + _ => error( + &mut errors, &f.sig, "main function must either not return a value, return `()` or return `!`", ), @@ -127,26 +130,99 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result TokenStream::new(), + (Some(x), _) | (None, Some(x)) if x == "" => TokenStream::new(), + (Some(x), _) | (None, Some(x)) => match TokenStream::from_str(x) { + Ok(x) => quote!(#[#x]), + Err(e) => { + error(&mut errors, &f.sig, e); + TokenStream::new() + } + }, + }; - let f_body = f.block; + let executor = match (args.executor.as_deref(), arch.executor_required) { + (None, true) => { + error( + &mut errors, + &f.sig, + "\ +No architecture selected for embassy-executor. Make sure you've enabled one of the `arch-*` features in your Cargo.toml. + +Alternatively, if you would like to use a custom executor implementation, specify it with the `executor` argument. +For example: `#[embassy_executor::main(entry = ..., executor = \"some_crate::Executor\")]", + ); + "" + } + (Some(x), _) => x, + (None, _) => "::embassy_executor::Executor", + }; + + let executor = TokenStream::from_str(executor).unwrap_or_else(|e| { + error(&mut errors, &f.sig, e); + TokenStream::new() + }); + + let f_body = f.body; let out = &f.sig.output; + let (main_ret, mut main_body) = match arch.flavor { + Flavor::Standard => ( + quote!(!), + quote! { + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + let mut executor = #executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + }, + ), + Flavor::Wasm => ( + quote!(Result<(), wasm_bindgen::JsValue>), + quote! { + let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(#executor::new())); + + executor.start(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }); + + Ok(()) + }, + ), + }; + + let mut main_attrs = TokenStream::new(); + for attr in f.attrs { + main_attrs.extend(quote!(#attr)); + } + + if !errors.is_empty() { + main_body = quote! {loop{}}; + } + let result = quote! { #[::embassy_executor::task()] + #[allow(clippy::future_not_send)] async fn __embassy_main(#fargs) #out { #f_body } - unsafe fn __make_static(t: &mut T) -> &'static mut T { - ::core::mem::transmute(t) + #entry + #main_attrs + fn main() -> #main_ret { + #main_body } - #main + #errors }; - Ok(result) + result } diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs index 96c6267b2..91d6beee8 100644 --- a/embassy-executor-macros/src/macros/task.rs +++ b/embassy-executor-macros/src/macros/task.rs @@ -1,48 +1,78 @@ +use std::str::FromStr; + use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; -use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type}; +use syn::visit::{self, Visit}; +use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; -use crate::util::ctxt::Ctxt; +use crate::util::*; -#[derive(Debug, FromMeta)] +#[derive(Debug, FromMeta, Default)] struct Args { #[darling(default)] pool_size: Option, + /// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`. + #[darling(default)] + embassy_executor: Option, } -pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { - let args = Args::from_list(args).map_err(|e| e.write_errors())?; +pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { + let mut errors = TokenStream::new(); + + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() + } + }; let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { attrs: vec![], lit: Lit::Int(LitInt::new("1", Span::call_site())), })); - let ctxt = Ctxt::new(); + let embassy_executor = args + .embassy_executor + .unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap())); if f.sig.asyncness.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must be async"); + error(&mut errors, &f.sig, "task functions must be async"); } if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&f.sig, "task functions must not be generic"); + error(&mut errors, &f.sig, "task functions must not be generic"); } if !f.sig.generics.where_clause.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not have `where` clauses"); + error(&mut errors, &f.sig, "task functions must not have `where` clauses"); } if !f.sig.abi.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not have an ABI qualifier"); + error(&mut errors, &f.sig, "task functions must not have an ABI qualifier"); } if !f.sig.variadic.is_none() { - ctxt.error_spanned_by(&f.sig, "task functions must not be variadic"); + error(&mut errors, &f.sig, "task functions must not be variadic"); } match &f.sig.output { ReturnType::Default => {} ReturnType::Type(_, ty) => match &**ty { Type::Tuple(tuple) if tuple.elems.is_empty() => {} Type::Never(_) => {} - _ => ctxt.error_spanned_by( + _ => error( + &mut errors, &f.sig, "task functions must either not return a value, return `()` or return `!`", ), @@ -55,26 +85,31 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { - ctxt.error_spanned_by(arg, "task functions must not have receiver arguments"); + error(&mut errors, arg, "task functions must not have `self` arguments"); } - syn::FnArg::Typed(t) => match t.pat.as_mut() { - syn::Pat::Ident(id) => { - id.mutability = None; - args.push((id.clone(), t.attrs.clone())); + syn::FnArg::Typed(t) => { + check_arg_ty(&mut errors, &t.ty); + match t.pat.as_mut() { + syn::Pat::Ident(id) => { + id.mutability = None; + args.push((id.clone(), t.attrs.clone())); + } + _ => { + error( + &mut errors, + arg, + "pattern matching in task arguments is not yet supported", + ); + } } - _ => { - ctxt.error_spanned_by(arg, "pattern matching in task arguments is not yet supported"); - } - }, + } } } - ctxt.check()?; - let task_ident = f.sig.ident.clone(); let task_inner_ident = format_ident!("__{}_task", task_ident); - let mut task_inner = f; + let mut task_inner = f.clone(); let visibility = task_inner.vis.clone(); task_inner.vis = syn::Visibility::Inherited; task_inner.sig.ident = task_inner_ident.clone(); @@ -91,35 +126,54 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result ::embassy_executor::SpawnToken { - trait _EmbassyInternalTaskTrait { - type Fut: ::core::future::Future + 'static; - fn construct(#fargs) -> Self::Fut; - } - - impl _EmbassyInternalTaskTrait for () { - type Fut = impl core::future::Future + 'static; - fn construct(#fargs) -> Self::Fut { - #task_inner_ident(#(#full_args,)*) - } - } - - const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = ::embassy_executor::raw::TaskPool::new(); - unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } + let mut task_outer_body = quote! { + trait _EmbassyInternalTaskTrait { + type Fut: ::core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut; } + + impl _EmbassyInternalTaskTrait for () { + type Fut = impl core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut { + #task_inner_ident(#(#full_args,)*) + } + } + + const POOL_SIZE: usize = #pool_size; + static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new(); + unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } }; #[cfg(not(feature = "nightly"))] - let mut task_outer: ItemFn = parse_quote! { - #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken { - const POOL_SIZE: usize = #pool_size; - static POOL: ::embassy_executor::_export::TaskPoolRef = ::embassy_executor::_export::TaskPoolRef::new(); - unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } + let mut task_outer_body = quote! { + const fn __task_pool_get(_: F) -> &'static #embassy_executor::raw::TaskPool + where + F: #embassy_executor::_export::TaskFn, + Fut: ::core::future::Future + 'static, + { + unsafe { &*POOL.get().cast() } } + + const POOL_SIZE: usize = #pool_size; + static POOL: #embassy_executor::_export::TaskPoolHolder< + {#embassy_executor::_export::task_pool_size::<_, _, _, POOL_SIZE>(#task_inner_ident)}, + {#embassy_executor::_export::task_pool_align::<_, _, _, POOL_SIZE>(#task_inner_ident)}, + > = unsafe { ::core::mem::transmute(#embassy_executor::_export::task_pool_new::<_, _, _, POOL_SIZE>(#task_inner_ident)) }; + unsafe { __task_pool_get(#task_inner_ident)._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } }; - task_outer.attrs.append(&mut task_inner.attrs.clone()); + let task_outer_attrs = task_inner.attrs.clone(); + + if !errors.is_empty() { + task_outer_body = quote! { + #![allow(unused_variables, unreachable_code)] + let _x: #embassy_executor::SpawnToken<()> = ::core::todo!(); + _x + }; + } + + // Copy the generics + where clause to avoid more spurious errors. + let generics = &f.sig.generics; + let where_clause = &f.sig.generics.where_clause; let result = quote! { // This is the user's task function, renamed. @@ -129,8 +183,49 @@ pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result #embassy_executor::SpawnToken #where_clause{ + #task_outer_body + } + + #errors }; - Ok(result) + result +} + +fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { + struct Visitor<'a> { + errors: &'a mut TokenStream, + } + + impl<'a, 'ast> Visit<'ast> for Visitor<'a> { + fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) { + // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`. + if i.lifetime.is_none() { + error( + self.errors, + i.and_token, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + visit::visit_type_reference(self, i); + } + + fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { + if i.ident.to_string() != "static" { + error( + self.errors, + i, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + } + + fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) { + error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic."); + } + } + + Visit::visit_type(&mut Visitor { errors }, ty); } diff --git a/embassy-executor-macros/src/util.rs b/embassy-executor-macros/src/util.rs new file mode 100644 index 000000000..ebd032a62 --- /dev/null +++ b/embassy-executor-macros/src/util.rs @@ -0,0 +1,74 @@ +use std::fmt::Display; + +use proc_macro2::{TokenStream, TokenTree}; +use quote::{ToTokens, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility}; + +pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(error.into_compile_error()); + tokens +} + +pub fn error(s: &mut TokenStream, obj: A, msg: T) { + s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error()) +} + +/// Function signature and body. +/// +/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of +/// parsing it. This makes the macro not error if there's a syntax error in the body, +/// which helps IDE autocomplete work better. +#[derive(Debug, Clone)] +pub struct ItemFn { + pub attrs: Vec, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: token::Brace, + pub body: TokenStream, +} + +impl Parse for ItemFn { + fn parse(input: ParseStream) -> syn::Result { + let mut attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let sig: Signature = input.parse()?; + + let content; + let brace_token = braced!(content in input); + while content.peek(Token![#]) && content.peek2(Token![!]) { + let content2; + attrs.push(Attribute { + pound_token: content.parse()?, + style: AttrStyle::Inner(content.parse()?), + bracket_token: bracketed!(content2 in content), + meta: content2.parse()?, + }); + } + + let mut body = Vec::new(); + while !content.is_empty() { + body.push(content.parse::()?); + } + let body = body.into_iter().collect(); + + Ok(ItemFn { + attrs, + vis, + sig, + brace_token, + body, + }) + } +} + +impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer))); + self.vis.to_tokens(tokens); + self.sig.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.body.clone()); + }); + } +} diff --git a/embassy-executor-macros/src/util/ctxt.rs b/embassy-executor-macros/src/util/ctxt.rs deleted file mode 100644 index 9c78cda01..000000000 --- a/embassy-executor-macros/src/util/ctxt.rs +++ /dev/null @@ -1,72 +0,0 @@ -// nifty utility borrowed from serde :) -// https://github.com/serde-rs/serde/blob/master/serde_derive/src/internals/ctxt.rs - -use std::cell::RefCell; -use std::fmt::Display; -use std::thread; - -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; - -/// A type to collect errors together and format them. -/// -/// Dropping this object will cause a panic. It must be consumed using `check`. -/// -/// References can be shared since this type uses run-time exclusive mut checking. -#[derive(Default)] -pub struct Ctxt { - // The contents will be set to `None` during checking. This is so that checking can be - // enforced. - errors: RefCell>>, -} - -impl Ctxt { - /// Create a new context object. - /// - /// This object contains no errors, but will still trigger a panic if it is not `check`ed. - pub fn new() -> Self { - Ctxt { - errors: RefCell::new(Some(Vec::new())), - } - } - - /// Add an error to the context object with a tokenenizable object. - /// - /// The object is used for spanning in error messages. - pub fn error_spanned_by(&self, obj: A, msg: T) { - self.errors - .borrow_mut() - .as_mut() - .unwrap() - // Curb monomorphization from generating too many identical methods. - .push(syn::Error::new_spanned(obj.into_token_stream(), msg)); - } - - /// Add one of Syn's parse errors. - #[allow(unused)] - pub fn syn_error(&self, err: syn::Error) { - self.errors.borrow_mut().as_mut().unwrap().push(err); - } - - /// Consume this object, producing a formatted error string if there are errors. - pub fn check(self) -> Result<(), TokenStream> { - let errors = self.errors.borrow_mut().take().unwrap(); - match errors.len() { - 0 => Ok(()), - _ => Err(to_compile_errors(errors)), - } - } -} - -fn to_compile_errors(errors: Vec) -> proc_macro2::TokenStream { - let compile_errors = errors.iter().map(syn::Error::to_compile_error); - quote!(#(#compile_errors)*) -} - -impl Drop for Ctxt { - fn drop(&mut self) { - if !thread::panicking() && self.errors.borrow().is_some() { - panic!("forgot to check for errors"); - } - } -} diff --git a/embassy-executor-macros/src/util/mod.rs b/embassy-executor-macros/src/util/mod.rs deleted file mode 100644 index 28702809e..000000000 --- a/embassy-executor-macros/src/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ctxt; diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 5582b56ec..608c67724 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -5,13 +5,46 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## unreleased + +- Added support for Cortex-A and Cortex-R + +## 0.7.0 - 2025-01-02 + +- Performance optimizations. +- Remove feature `integrated-timers`. Starting with `embassy-time-driver` v0.2, `embassy-time` v0.4 the timer queue is now part of the time driver, so it's no longer the executor's responsibility. Therefore, `embassy-executor` no longer provides an `embassy-time-queue-driver` implementation. +- Added the possibility for timer driver implementations to store arbitrary data in task headers. This can be used to make a timer queue intrusive list, similar to the previous `integrated-timers` feature. Payload size is controlled by the `timer-item-payload-size-X` features. +- Added `TaskRef::executor` to obtain a reference to a task's executor + +## 0.6.3 - 2024-11-12 + +- Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82. +- Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m. + +## 0.6.2 - 2024-11-06 + +- The `nightly` feature no longer requires `nightly-2024-09-06` or newer. + +## 0.6.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. +- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature. +- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer. +- Improve macro error messages. ## 0.6.0 - 2024-08-05 - Add collapse_debuginfo to fmt.rs macros. -- initial support for avr -- use nightly waker_getters APIs +- initial support for AVR +- use nightly waker_getters APIs + +## 0.5.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. ## 0.5.0 - 2024-01-11 diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 5984cc49c..f014ccf30 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor" -version = "0.6.0" +version = "0.7.0" edition = "2021" license = "MIT OR Apache-2.0" description = "async/await executor designed for embedded usage" @@ -29,13 +29,12 @@ targets = ["thumbv7em-none-eabi"] features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } -rtos-trace = { version = "0.1.2", optional = true } +rtos-trace = { version = "0.1.3", optional = true } -embassy-executor-macros = { version = "0.5.0", path = "../embassy-executor-macros" } -embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true } -embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true } +embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } critical-section = "1.1" document-features = "0.2.7" @@ -46,16 +45,20 @@ portable-atomic = { version = "1.5", optional = true } # arch-cortex-m dependencies cortex-m = { version = "0.7.6", optional = true } +# arch-cortex-ar dependencies +cortex-ar = { version = "0.2", optional = true } + # arch-wasm dependencies 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 } +avr-device = { version = "0.7.0", optional = true } [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } - +trybuild = "1.0" +embassy-sync = { path = "../embassy-sync" } [features] @@ -67,21 +70,22 @@ nightly = ["embassy-executor-macros/nightly"] # See: https://github.com/embassy-rs/embassy/pull/1263 turbowakers = [] -## Use the executor-integrated `embassy-time` timer queue. -integrated-timers = ["dep:embassy-time-driver", "dep:embassy-time-queue-driver"] - #! ### Architecture _arch = [] # some arch was picked ## std -arch-std = ["_arch", "critical-section/std"] +arch-std = ["_arch"] ## Cortex-M arch-cortex-m = ["_arch", "dep:cortex-m"] +## Cortex-A/R +arch-cortex-ar = ["_arch", "dep:cortex-ar"] ## RISC-V 32 arch-riscv32 = ["_arch"] ## WASM arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] ## AVR arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] +## spin (architecture agnostic; never sleeps) +arch-spin = ["_arch"] #! ### Executor @@ -89,98 +93,23 @@ arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] executor-thread = [] ## Enable the interrupt-mode executor (available in Cortex-M only) executor-interrupt = [] +## Enable tracing support (adds some overhead) +trace = [] +## Enable support for rtos-trace framework +rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"] -#! ### Task Arena Size -#! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`. -#! -#!

-#! Preconfigured Task Arena Sizes: -#! -#! +#! ### Timer Item Payload Size +#! Sets the size of the payload for timer items, allowing integrated timer implementors to store +#! additional data in the timer item. The payload field will be aligned to this value as well. +#! If these features are not defined, the timer item will contain no payload field. -# BEGIN AUTOGENERATED CONFIG FEATURES -# Generated by gen_config.py. DO NOT EDIT. -## 64 -task-arena-size-64 = [] -## 128 -task-arena-size-128 = [] -## 192 -task-arena-size-192 = [] -## 256 -task-arena-size-256 = [] -## 320 -task-arena-size-320 = [] -## 384 -task-arena-size-384 = [] -## 512 -task-arena-size-512 = [] -## 640 -task-arena-size-640 = [] -## 768 -task-arena-size-768 = [] -## 1024 -task-arena-size-1024 = [] -## 1280 -task-arena-size-1280 = [] -## 1536 -task-arena-size-1536 = [] -## 2048 -task-arena-size-2048 = [] -## 2560 -task-arena-size-2560 = [] -## 3072 -task-arena-size-3072 = [] -## 4096 (default) -task-arena-size-4096 = [] # Default -## 5120 -task-arena-size-5120 = [] -## 6144 -task-arena-size-6144 = [] -## 8192 -task-arena-size-8192 = [] -## 10240 -task-arena-size-10240 = [] -## 12288 -task-arena-size-12288 = [] -## 16384 -task-arena-size-16384 = [] -## 20480 -task-arena-size-20480 = [] -## 24576 -task-arena-size-24576 = [] -## 32768 -task-arena-size-32768 = [] -## 40960 -task-arena-size-40960 = [] -## 49152 -task-arena-size-49152 = [] -## 65536 -task-arena-size-65536 = [] -## 81920 -task-arena-size-81920 = [] -## 98304 -task-arena-size-98304 = [] -## 131072 -task-arena-size-131072 = [] -## 163840 -task-arena-size-163840 = [] -## 196608 -task-arena-size-196608 = [] -## 262144 -task-arena-size-262144 = [] -## 327680 -task-arena-size-327680 = [] -## 393216 -task-arena-size-393216 = [] -## 524288 -task-arena-size-524288 = [] -## 655360 -task-arena-size-655360 = [] -## 786432 -task-arena-size-786432 = [] -## 1048576 -task-arena-size-1048576 = [] +_timer-item-payload = [] # A size was picked -# END AUTOGENERATED CONFIG FEATURES - -#!
+## 1 bytes +timer-item-payload-size-1 = ["_timer-item-payload"] +## 2 bytes +timer-item-payload-size-2 = ["_timer-item-payload"] +## 4 bytes +timer-item-payload-size-4 = ["_timer-item-payload"] +## 8 bytes +timer-item-payload-size-8 = ["_timer-item-payload"] diff --git a/embassy-executor/README.md b/embassy-executor/README.md index aa9d59907..85f15edbb 100644 --- a/embassy-executor/README.md +++ b/embassy-executor/README.md @@ -3,35 +3,10 @@ An async/await executor designed for embedded usage. - No `alloc`, no heap needed. -- With nightly Rust, task futures can be fully statically allocated. +- Tasks are statically allocated. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible. - No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. - Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`. - No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. - Efficient polling: a wake will only poll the woken task, not all of them. - Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. - Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. - -## Task arena - -When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator). - -If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks. - -Tasks are allocated from the arena when spawned for the first time. If the task exists, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load). - -The arena size can be configured in two ways: - -- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values - is available, see [Task Area Sizes](#task-arena-size) for reference. -- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example - `EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. - Any value can be set, unlike with Cargo features. - -Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting -with different values, compilation fails. - -## Statically allocating tasks - -When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible. - -The configured arena size is ignored, no arena is used at all. diff --git a/embassy-executor/build.rs b/embassy-executor/build.rs index 8a41d7503..37becde3e 100644 --- a/embassy-executor/build.rs +++ b/embassy-executor/build.rs @@ -1,99 +1,7 @@ -use std::collections::HashMap; -use std::fmt::Write; -use std::path::PathBuf; -use std::{env, fs}; - #[path = "./build_common.rs"] mod common; -static CONFIGS: &[(&str, usize)] = &[ - // BEGIN AUTOGENERATED CONFIG FEATURES - // Generated by gen_config.py. DO NOT EDIT. - ("TASK_ARENA_SIZE", 4096), - // END AUTOGENERATED CONFIG FEATURES -]; - -struct ConfigState { - value: usize, - seen_feature: bool, - seen_env: bool, -} - fn main() { - let crate_name = env::var("CARGO_PKG_NAME") - .unwrap() - .to_ascii_uppercase() - .replace('-', "_"); - - // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any - // other file changed. - println!("cargo:rerun-if-changed=build.rs"); - - // Rebuild if config envvar changed. - for (name, _) in CONFIGS { - println!("cargo:rerun-if-env-changed={crate_name}_{name}"); - } - - let mut configs = HashMap::new(); - for (name, default) in CONFIGS { - configs.insert( - *name, - ConfigState { - value: *default, - seen_env: false, - seen_feature: false, - }, - ); - } - - let prefix = format!("{crate_name}_"); - for (var, value) in env::vars() { - if let Some(name) = var.strip_prefix(&prefix) { - let Some(cfg) = configs.get_mut(name) else { - panic!("Unknown env var {name}") - }; - - let Ok(value) = value.parse::() else { - panic!("Invalid value for env var {name}: {value}") - }; - - cfg.value = value; - cfg.seen_env = true; - } - - if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { - if let Some(i) = feature.rfind('_') { - let name = &feature[..i]; - let value = &feature[i + 1..]; - if let Some(cfg) = configs.get_mut(name) { - let Ok(value) = value.parse::() else { - panic!("Invalid value for feature {name}: {value}") - }; - - // envvars take priority. - if !cfg.seen_env { - if cfg.seen_feature { - panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); - } - - cfg.value = value; - cfg.seen_feature = true; - } - } - } - } - } - - let mut data = String::new(); - - for (name, cfg) in &configs { - writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); - } - - let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); - fs::write(out_file, data).unwrap(); - let mut rustc_cfgs = common::CfgSet::new(); common::set_target_cfgs(&mut rustc_cfgs); } diff --git a/embassy-executor/build_common.rs b/embassy-executor/build_common.rs index 4f24e6d37..b15a8369f 100644 --- a/embassy-executor/build_common.rs +++ b/embassy-executor/build_common.rs @@ -92,3 +92,35 @@ pub fn set_target_cfgs(cfgs: &mut CfgSet) { cfgs.set("has_fpu", target.ends_with("-eabihf")); } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompilerDate { + year: u16, + month: u8, + day: u8, +} + +impl CompilerDate { + fn parse(date: &str) -> Option { + let mut parts = date.split('-'); + let year = parts.next()?.parse().ok()?; + let month = parts.next()?.parse().ok()?; + let day = parts.next()?.parse().ok()?; + Some(Self { year, month, day }) + } +} + +impl PartialEq<&str> for CompilerDate { + fn eq(&self, other: &&str) -> bool { + let Some(other) = Self::parse(other) else { + return false; + }; + self.eq(&other) + } +} + +impl PartialOrd<&str> for CompilerDate { + fn partial_cmp(&self, other: &&str) -> Option { + Self::parse(other).map(|other| self.cmp(&other)) + } +} diff --git a/embassy-executor/src/arch/cortex_ar.rs b/embassy-executor/src/arch/cortex_ar.rs new file mode 100644 index 000000000..f9e2f3f7c --- /dev/null +++ b/embassy-executor/src/arch/cortex_ar.rs @@ -0,0 +1,84 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-cortex-ar`."); + +#[export_name = "__pender"] +#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] +fn __pender(context: *mut ()) { + // `context` is always `usize::MAX` created by `Executor::run`. + let context = context as usize; + + #[cfg(feature = "executor-thread")] + // Try to make Rust optimize the branching away if we only use thread mode. + if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { + cortex_ar::asm::sev(); + return; + } +} + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + pub(super) const THREAD_PENDER: usize = usize::MAX; + + use core::marker::PhantomData; + + use cortex_ar::asm::wfe; + pub use embassy_executor_macros::main_cortex_ar as main; + + use crate::{raw, Spawner}; + + /// Thread mode executor, using WFE/SEV. + /// + /// This is the simplest and most common kind of executor. It runs on + /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction + /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction + /// is executed, to make the `WFE` exit from sleep and poll the task. + /// + /// This executor allows for ultra low power consumption for chips where `WFE` + /// triggers low-power sleep without extra steps. If your chip requires extra steps, + /// you may use [`raw::Executor`] directly to program custom behavior. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(THREAD_PENDER as *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 { + self.inner.poll(); + } + wfe(); + } + } + } +} diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 5c517e0a2..1c9ddd8a0 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -143,7 +143,7 @@ mod interrupt { /// If this is not the case, you may use an interrupt from any unused peripheral. /// /// It is somewhat more complex to use, it's recommended to use the thread-mode - /// [`Executor`] instead, if it works for your use case. + /// [`Executor`](crate::Executor) instead, if it works for your use case. pub struct InterruptExecutor { started: Mutex>, executor: UnsafeCell>, @@ -179,11 +179,11 @@ mod interrupt { /// The executor keeps running in the background through the interrupt. /// /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] - /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a + /// is returned instead of a [`Spawner`](crate::Spawner) because the executor effectively runs in a /// different "thread" (the interrupt), so spawning tasks on it is effectively /// sending them. /// - /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from + /// To obtain a [`Spawner`](crate::Spawner) for this executor, use [`Spawner::for_current_executor()`](crate::Spawner::for_current_executor()) from /// a task running in it. /// /// # Interrupt requirements @@ -195,6 +195,7 @@ mod interrupt { /// You must set the interrupt priority before calling this method. You MUST NOT /// do it after. /// + /// [`SendSpawner`]: crate::SendSpawner pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { if critical_section::with(|cs| self.started.borrow(cs).replace(true)) { panic!("InterruptExecutor::start() called multiple times on the same executor."); @@ -215,7 +216,7 @@ mod interrupt { /// Get a SendSpawner for this executor /// - /// This returns a [`SendSpawner`] you can use to spawn tasks on this + /// This returns a [`SendSpawner`](crate::SendSpawner) you can use to spawn tasks on this /// executor. /// /// This MUST only be called on an executor that has already been started. diff --git a/embassy-executor/src/arch/spin.rs b/embassy-executor/src/arch/spin.rs new file mode 100644 index 000000000..340023620 --- /dev/null +++ b/embassy-executor/src/arch/spin.rs @@ -0,0 +1,58 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-spin`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_spin as main; + + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) {} + + /// Spin 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 { self.inner.poll() }; + } + } + } +} diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 553ed76d3..dfe420bab 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -1,5 +1,4 @@ #![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(waker_getters))] #![allow(clippy::new_without_default)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] @@ -24,120 +23,186 @@ macro_rules! check_at_most_one { check_at_most_one!(@amo [$($f)*] [$($f)*] []); }; } -check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm",); +check_at_most_one!( + "arch-avr", + "arch-cortex-m", + "arch-cortex-ar", + "arch-riscv32", + "arch-std", + "arch-wasm", + "arch-spin", +); #[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-cortex-ar", path = "arch/cortex_ar.rs")] #[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] #[cfg_attr(feature = "arch-std", path = "arch/std.rs")] #[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] +#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")] mod arch; #[cfg(feature = "_arch")] #[allow(unused_imports)] // don't warn if the module is empty. pub use arch::*; +#[cfg(not(feature = "_arch"))] +pub use embassy_executor_macros::main_unspecified as main; pub mod raw; mod spawner; pub use spawner::*; -mod config { - #![allow(unused)] - include!(concat!(env!("OUT_DIR"), "/config.rs")); -} - /// Implementation details for embassy macros. /// Do not use. Used for macros and HALs only. Not covered by semver guarantees. #[doc(hidden)] #[cfg(not(feature = "nightly"))] pub mod _export { - use core::alloc::Layout; - use core::cell::{Cell, UnsafeCell}; + use core::cell::UnsafeCell; use core::future::Future; use core::mem::MaybeUninit; - use core::ptr::null_mut; - - use critical_section::{CriticalSection, Mutex}; use crate::raw::TaskPool; - struct Arena { - buf: UnsafeCell>, - ptr: Mutex>, + pub trait TaskFn: Copy { + type Fut: Future + 'static; } - unsafe impl Sync for Arena {} - unsafe impl Send for Arena {} - - impl Arena { - const fn new() -> Self { - Self { - buf: UnsafeCell::new(MaybeUninit::uninit()), - ptr: Mutex::new(Cell::new(null_mut())), + macro_rules! task_fn_impl { + ($($Tn:ident),*) => { + impl TaskFn<($($Tn,)*)> for F + where + F: Copy + FnOnce($($Tn,)*) -> Fut, + Fut: Future + 'static, + { + type Fut = Fut; } - } + }; + } - fn alloc(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit { - let layout = Layout::new::(); + task_fn_impl!(); + task_fn_impl!(T0); + task_fn_impl!(T0, T1); + task_fn_impl!(T0, T1, T2); + task_fn_impl!(T0, T1, T2, T3); + task_fn_impl!(T0, T1, T2, T3, T4); + task_fn_impl!(T0, T1, T2, T3, T4, T5); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14); + task_fn_impl!(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15); - let start = self.buf.get().cast::(); - let end = unsafe { start.add(N) }; + #[allow(private_bounds)] + #[repr(C)] + pub struct TaskPoolHolder + where + Align: Alignment, + { + data: UnsafeCell<[MaybeUninit; SIZE]>, + align: Align, + } - let mut ptr = self.ptr.borrow(cs).get(); - if ptr.is_null() { - ptr = self.buf.get().cast::(); - } + unsafe impl Send for TaskPoolHolder where Align: Alignment {} + unsafe impl Sync for TaskPoolHolder where Align: Alignment {} - let bytes_left = (end as usize) - (ptr as usize); - let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); - - if align_offset + layout.size() > bytes_left { - panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/"); - } - - let res = unsafe { ptr.add(align_offset) }; - let ptr = unsafe { ptr.add(align_offset + layout.size()) }; - - self.ptr.borrow(cs).set(ptr); - - unsafe { &mut *(res as *mut MaybeUninit) } + #[allow(private_bounds)] + impl TaskPoolHolder + where + Align: Alignment, + { + pub const fn get(&self) -> *const u8 { + self.data.get().cast() } } - static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); - - pub struct TaskPoolRef { - // type-erased `&'static mut TaskPool` - // Needed because statics can't have generics. - ptr: Mutex>, + pub const fn task_pool_size(_: F) -> usize + where + F: TaskFn, + Fut: Future + 'static, + { + size_of::>() } - unsafe impl Sync for TaskPoolRef {} - unsafe impl Send for TaskPoolRef {} - impl TaskPoolRef { - pub const fn new() -> Self { - Self { - ptr: Mutex::new(Cell::new(null_mut())), - } - } + pub const fn task_pool_align(_: F) -> usize + where + F: TaskFn, + Fut: Future + 'static, + { + align_of::>() + } - /// Get the pool for this ref, allocating it from the arena the first time. - /// - /// safety: for a given TaskPoolRef instance, must always call with the exact - /// same generic params. - pub unsafe fn get(&'static self) -> &'static TaskPool { - critical_section::with(|cs| { - let ptr = self.ptr.borrow(cs); - if ptr.get().is_null() { - let pool = ARENA.alloc::>(cs); - pool.write(TaskPool::new()); - ptr.set(pool as *mut _ as _); + pub const fn task_pool_new(_: F) -> TaskPool + where + F: TaskFn, + Fut: Future + 'static, + { + TaskPool::new() + } + + #[allow(private_bounds)] + #[repr(transparent)] + pub struct Align([::Archetype; 0]) + where + Self: Alignment; + + trait Alignment { + /// A zero-sized type of particular alignment. + type Archetype: Copy + Eq + PartialEq + Send + Sync + Unpin; + } + + macro_rules! aligns { + ($($AlignX:ident: $n:literal,)*) => { + $( + #[derive(Copy, Clone, Eq, PartialEq)] + #[repr(align($n))] + struct $AlignX {} + impl Alignment for Align<$n> { + type Archetype = $AlignX; } - - unsafe { &*(ptr.get() as *const _) } - }) - } + )* + }; } + + aligns!( + Align1: 1, + Align2: 2, + Align4: 4, + Align8: 8, + Align16: 16, + Align32: 32, + Align64: 64, + Align128: 128, + Align256: 256, + Align512: 512, + Align1024: 1024, + Align2048: 2048, + Align4096: 4096, + Align8192: 8192, + Align16384: 16384, + ); + #[cfg(any(target_pointer_width = "32", target_pointer_width = "64"))] + aligns!( + Align32768: 32768, + Align65536: 65536, + Align131072: 131072, + Align262144: 262144, + Align524288: 524288, + Align1048576: 1048576, + Align2097152: 2097152, + Align4194304: 4194304, + Align8388608: 8388608, + Align16777216: 16777216, + Align33554432: 33554432, + Align67108864: 67108864, + Align134217728: 134217728, + Align268435456: 268435456, + Align536870912: 536870912, + ); } diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index d9ea5c005..913da2e25 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -11,13 +11,14 @@ #[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")] mod run_queue; -#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")] +#[cfg_attr(all(cortex_m, target_has_atomic = "32"), path = "state_atomics_arm.rs")] #[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")] #[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] mod state; -#[cfg(feature = "integrated-timers")] -mod timer_queue; +pub mod timer_queue; +#[cfg(feature = "trace")] +pub mod trace; pub(crate) mod util; #[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] mod waker; @@ -27,12 +28,13 @@ use core::marker::PhantomData; use core::mem; use core::pin::Pin; use core::ptr::NonNull; +#[cfg(not(feature = "arch-avr"))] +use core::sync::atomic::AtomicPtr; +use core::sync::atomic::Ordering; use core::task::{Context, Poll}; -#[cfg(feature = "integrated-timers")] -use embassy_time_driver::AlarmHandle; -#[cfg(feature = "rtos-trace")] -use rtos_trace::trace; +#[cfg(feature = "arch-avr")] +use portable_atomic::AtomicPtr; use self::run_queue::{RunQueue, RunQueueItem}; use self::state::State; @@ -41,20 +43,62 @@ pub use self::waker::task_from_waker; use super::SpawnToken; /// Raw task header for use in task pointers. +/// +/// A task can be in one of the following states: +/// +/// - Not spawned: the task is ready to spawn. +/// - `SPAWNED`: the task is currently spawned and may be running. +/// - `RUN_ENQUEUED`: the task is enqueued to be polled. Note that the task may be `!SPAWNED`. +/// In this case, the `RUN_ENQUEUED` state will be cleared when the task is next polled, without +/// polling the task's future. +/// +/// A task's complete life cycle is as follows: +/// +/// ```text +/// ┌────────────┐ ┌────────────────────────┐ +/// │Not spawned │◄─5┤Not spawned|Run enqueued│ +/// │ ├6─►│ │ +/// └─────┬──────┘ └──────▲─────────────────┘ +/// 1 │ +/// │ ┌────────────┘ +/// │ 4 +/// ┌─────▼────┴─────────┐ +/// │Spawned|Run enqueued│ +/// │ │ +/// └─────┬▲─────────────┘ +/// 2│ +/// │3 +/// ┌─────▼┴─────┐ +/// │ Spawned │ +/// │ │ +/// └────────────┘ +/// ``` +/// +/// Transitions: +/// - 1: Task is spawned - `AvailableTask::claim -> Executor::spawn` +/// - 2: During poll - `RunQueue::dequeue_all -> State::run_dequeue` +/// - 3: Task wakes itself, waker wakes task, or task exits - `Waker::wake -> wake_task -> State::run_enqueue` +/// - 4: A run-queued task exits - `TaskStorage::poll -> Poll::Ready` +/// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`. +/// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue` pub(crate) struct TaskHeader { pub(crate) state: State, pub(crate) run_queue_item: RunQueueItem, - pub(crate) executor: SyncUnsafeCell>, + pub(crate) executor: AtomicPtr, poll_fn: SyncUnsafeCell>, - #[cfg(feature = "integrated-timers")] - pub(crate) expires_at: SyncUnsafeCell, - #[cfg(feature = "integrated-timers")] + /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. pub(crate) timer_queue_item: timer_queue::TimerQueueItem, + #[cfg(feature = "trace")] + pub(crate) name: Option<&'static str>, + #[cfg(feature = "trace")] + pub(crate) id: u32, + #[cfg(feature = "trace")] + all_tasks_next: AtomicPtr, } /// This is essentially a `&'static TaskStorage` where the type of the future has been erased. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub struct TaskRef { ptr: NonNull, } @@ -76,10 +120,31 @@ impl TaskRef { } } + /// # Safety + /// + /// The result of this function must only be compared + /// for equality, or stored, but not used. + pub const unsafe fn dangling() -> Self { + Self { + ptr: NonNull::dangling(), + } + } + pub(crate) fn header(self) -> &'static TaskHeader { unsafe { self.ptr.as_ref() } } + /// Returns a reference to the executor that the task is currently running on. + pub unsafe fn executor(self) -> Option<&'static Executor> { + let executor = self.header().executor.load(Ordering::Relaxed); + executor.as_ref().map(|e| Executor::wrap(e)) + } + + /// Returns a reference to the timer queue item. + pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem { + &self.header().timer_queue_item + } + /// The returned pointer is valid for the entire TaskStorage. pub(crate) fn as_ptr(self) -> *const TaskHeader { self.ptr.as_ptr() @@ -107,6 +172,10 @@ pub struct TaskStorage { future: UninitCell, // Valid if STATE_SPAWNED } +unsafe fn poll_exited(_p: TaskRef) { + // Nothing to do, the task is already !SPAWNED and dequeued. +} + impl TaskStorage { const NEW: Self = Self::new(); @@ -116,14 +185,17 @@ impl TaskStorage { raw: TaskHeader { state: State::new(), run_queue_item: RunQueueItem::new(), - executor: SyncUnsafeCell::new(None), + executor: AtomicPtr::new(core::ptr::null_mut()), // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` poll_fn: SyncUnsafeCell::new(None), - #[cfg(feature = "integrated-timers")] - expires_at: SyncUnsafeCell::new(0), - #[cfg(feature = "integrated-timers")] timer_queue_item: timer_queue::TimerQueueItem::new(), + #[cfg(feature = "trace")] + name: None, + #[cfg(feature = "trace")] + id: 0, + #[cfg(feature = "trace")] + all_tasks_next: AtomicPtr::new(core::ptr::null_mut()), }, future: UninitCell::uninit(), } @@ -151,18 +223,30 @@ impl TaskStorage { } unsafe fn poll(p: TaskRef) { - let this = &*(p.as_ptr() as *const TaskStorage); + let this = &*p.as_ptr().cast::>(); let future = Pin::new_unchecked(this.future.as_mut()); let waker = waker::from_task(p); let mut cx = Context::from_waker(&waker); match future.poll(&mut cx) { Poll::Ready(_) => { + #[cfg(feature = "trace")] + let exec_ptr: *const SyncExecutor = this.raw.executor.load(Ordering::Relaxed); + + // As the future has finished and this function will not be called + // again, we can safely drop the future here. this.future.drop_in_place(); + + // We replace the poll_fn with a despawn function, so that the task is cleaned up + // when the executor polls it next. + this.raw.poll_fn.set(Some(poll_exited)); + + // Make sure we despawn last, so that other threads can only spawn the task + // after we're done with it. this.raw.state.despawn(); - #[cfg(feature = "integrated-timers")] - this.raw.expires_at.set(u64::MAX); + #[cfg(feature = "trace")] + trace::task_end(exec_ptr, &p); } Poll::Pending => {} } @@ -316,26 +400,13 @@ impl Pender { pub(crate) struct SyncExecutor { run_queue: RunQueue, pender: Pender, - - #[cfg(feature = "integrated-timers")] - pub(crate) timer_queue: timer_queue::TimerQueue, - #[cfg(feature = "integrated-timers")] - alarm: AlarmHandle, } impl SyncExecutor { pub(crate) fn new(pender: Pender) -> Self { - #[cfg(feature = "integrated-timers")] - let alarm = unsafe { unwrap!(embassy_time_driver::allocate_alarm()) }; - Self { run_queue: RunQueue::new(), pender, - - #[cfg(feature = "integrated-timers")] - timer_queue: timer_queue::TimerQueue::new(), - #[cfg(feature = "integrated-timers")] - alarm, } } @@ -346,90 +417,50 @@ impl SyncExecutor { /// - `task` must be set up to run in this executor. /// - `task` must NOT be already enqueued (in this executor or another one). #[inline(always)] - unsafe fn enqueue(&self, task: TaskRef) { - #[cfg(feature = "rtos-trace")] - trace::task_ready_begin(task.as_ptr() as u32); + unsafe fn enqueue(&self, task: TaskRef, l: state::Token) { + #[cfg(feature = "trace")] + trace::task_ready_begin(self, &task); - if self.run_queue.enqueue(task) { + if self.run_queue.enqueue(task, l) { self.pender.pend(); } } - #[cfg(feature = "integrated-timers")] - fn alarm_callback(ctx: *mut ()) { - let this: &Self = unsafe { &*(ctx as *const Self) }; - this.pender.pend(); - } - pub(super) unsafe fn spawn(&'static self, task: TaskRef) { - task.header().executor.set(Some(self)); + task.header() + .executor + .store((self as *const Self).cast_mut(), Ordering::Relaxed); - #[cfg(feature = "rtos-trace")] - trace::task_new(task.as_ptr() as u32); + #[cfg(feature = "trace")] + trace::task_new(self, &task); - self.enqueue(task); + state::locked(|l| { + self.enqueue(task, l); + }) } /// # Safety /// /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. pub(crate) unsafe fn poll(&'static self) { - #[cfg(feature = "integrated-timers")] - embassy_time_driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ()); + #[cfg(feature = "trace")] + trace::poll_start(self); - #[allow(clippy::never_loop)] - loop { - #[cfg(feature = "integrated-timers")] - self.timer_queue - .dequeue_expired(embassy_time_driver::now(), wake_task_no_pend); + self.run_queue.dequeue_all(|p| { + let task = p.header(); - self.run_queue.dequeue_all(|p| { - let task = p.header(); + #[cfg(feature = "trace")] + trace::task_exec_begin(self, &p); - #[cfg(feature = "integrated-timers")] - task.expires_at.set(u64::MAX); + // Run the task + task.poll_fn.get().unwrap_unchecked()(p); - if !task.state.run_dequeue() { - // If task is not running, ignore it. This can happen in the following scenario: - // - Task gets dequeued, poll starts - // - While task is being polled, it gets woken. It gets placed in the queue. - // - Task poll finishes, returning done=true - // - RUNNING bit is cleared, but the task is already in the queue. - return; - } + #[cfg(feature = "trace")] + trace::task_exec_end(self, &p); + }); - #[cfg(feature = "rtos-trace")] - trace::task_exec_begin(p.as_ptr() as u32); - - // Run the task - task.poll_fn.get().unwrap_unchecked()(p); - - #[cfg(feature = "rtos-trace")] - trace::task_exec_end(); - - // Enqueue or update into timer_queue - #[cfg(feature = "integrated-timers")] - self.timer_queue.update(p); - }); - - #[cfg(feature = "integrated-timers")] - { - // If this is already in the past, set_alarm might return false - // In that case do another poll loop iteration. - let next_expiration = self.timer_queue.next_expiration(); - if embassy_time_driver::set_alarm(self.alarm, next_expiration) { - break; - } - } - - #[cfg(not(feature = "integrated-timers"))] - { - break; - } - } - - #[cfg(feature = "rtos-trace")] - trace::system_idle(); + #[cfg(feature = "trace")] + trace::executor_idle(self) } } @@ -516,6 +547,8 @@ impl Executor { /// /// # Safety /// + /// You must call `initialize` before calling this method. + /// /// You must NOT call `poll` reentrantly on the same executor. /// /// In particular, note that `poll` may call the pender synchronously. Therefore, you @@ -533,6 +566,11 @@ impl Executor { pub fn spawner(&'static self) -> super::Spawner { super::Spawner::new(self) } + + /// Get a unique ID for this Executor. + pub fn id(&'static self) -> usize { + &self.inner as *const SyncExecutor as usize + } } /// Wake a task by `TaskRef`. @@ -540,13 +578,13 @@ impl Executor { /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. pub fn wake_task(task: TaskRef) { let header = task.header(); - if header.state.run_enqueue() { + header.state.run_enqueue(|l| { // We have just marked the task as scheduled, so enqueue it. unsafe { - let executor = header.executor.get().unwrap_unchecked(); - executor.enqueue(task); + let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); + executor.enqueue(task, l); } - } + }); } /// Wake a task by `TaskRef` without calling pend. @@ -554,57 +592,11 @@ pub fn wake_task(task: TaskRef) { /// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. pub fn wake_task_no_pend(task: TaskRef) { let header = task.header(); - if header.state.run_enqueue() { + header.state.run_enqueue(|l| { // We have just marked the task as scheduled, so enqueue it. unsafe { - let executor = header.executor.get().unwrap_unchecked(); - executor.run_queue.enqueue(task); + let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); + executor.run_queue.enqueue(task, l); } - } + }); } - -#[cfg(feature = "integrated-timers")] -struct TimerQueue; - -#[cfg(feature = "integrated-timers")] -impl embassy_time_queue_driver::TimerQueue for TimerQueue { - fn schedule_wake(&'static self, at: u64, waker: &core::task::Waker) { - let task = waker::task_from_waker(waker); - let task = task.header(); - unsafe { - let expires_at = task.expires_at.get(); - task.expires_at.set(expires_at.min(at)); - } - } -} - -#[cfg(feature = "integrated-timers")] -embassy_time_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() { - // We don't know what tasks exist, so we can't send them. - } - #[cfg(feature = "integrated-timers")] - fn time() -> u64 { - 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 { - 0 - } -} - -#[cfg(feature = "rtos-trace")] -rtos_trace::global_os_callbacks! {Executor} diff --git a/embassy-executor/src/raw/run_queue_atomics.rs b/embassy-executor/src/raw/run_queue_atomics.rs index 90907cfda..ce511d79a 100644 --- a/embassy-executor/src/raw/run_queue_atomics.rs +++ b/embassy-executor/src/raw/run_queue_atomics.rs @@ -45,7 +45,7 @@ impl RunQueue { /// /// `item` must NOT be already enqueued in any queue. #[inline(always)] - pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool { + pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool { let mut was_empty = false; self.head @@ -81,6 +81,7 @@ impl RunQueue { // safety: there are no concurrent accesses to `next` next = unsafe { task.header().run_queue_item.next.get() }; + task.header().state.run_dequeue(); on_task(task); } } diff --git a/embassy-executor/src/raw/run_queue_critical_section.rs b/embassy-executor/src/raw/run_queue_critical_section.rs index ba59c8f29..86c4085ed 100644 --- a/embassy-executor/src/raw/run_queue_critical_section.rs +++ b/embassy-executor/src/raw/run_queue_critical_section.rs @@ -44,13 +44,11 @@ impl RunQueue { /// /// `item` must NOT be already enqueued in any queue. #[inline(always)] - pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool { - critical_section::with(|cs| { - let prev = self.head.borrow(cs).replace(Some(task)); - task.header().run_queue_item.next.borrow(cs).set(prev); + pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool { + let prev = self.head.borrow(cs).replace(Some(task)); + task.header().run_queue_item.next.borrow(cs).set(prev); - prev.is_none() - }) + prev.is_none() } /// Empty the queue, then call `on_task` for each task that was in the queue. @@ -65,9 +63,10 @@ impl RunQueue { // If the task re-enqueues itself, the `next` pointer will get overwritten. // Therefore, first read the next pointer, and only then process the task. - // safety: we know if the task is enqueued, no one else will touch the `next` pointer. - let cs = unsafe { CriticalSection::new() }; - next = task.header().run_queue_item.next.borrow(cs).get(); + critical_section::with(|cs| { + next = task.header().run_queue_item.next.borrow(cs).get(); + task.header().state.run_dequeue(cs); + }); on_task(task); } diff --git a/embassy-executor/src/raw/state_atomics.rs b/embassy-executor/src/raw/state_atomics.rs index e1279ac0b..e813548ae 100644 --- a/embassy-executor/src/raw/state_atomics.rs +++ b/embassy-executor/src/raw/state_atomics.rs @@ -1,21 +1,28 @@ -use core::sync::atomic::{AtomicU32, Ordering}; +use core::sync::atomic::{AtomicU8, Ordering}; + +#[derive(Clone, Copy)] +pub(crate) struct Token(()); + +/// Creates a token and passes it to the closure. +/// +/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. +pub(crate) fn locked(f: impl FnOnce(Token) -> R) -> R { + f(Token(())) +} /// Task is spawned (has a future) -pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +pub(crate) const STATE_SPAWNED: u8 = 1 << 0; /// Task is in the executor run queue -pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; -/// Task is in the executor timer queue -#[cfg(feature = "integrated-timers")] -pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; +pub(crate) const STATE_RUN_QUEUED: u8 = 1 << 1; pub(crate) struct State { - state: AtomicU32, + state: AtomicU8, } impl State { pub const fn new() -> State { Self { - state: AtomicU32::new(0), + state: AtomicU8::new(0), } } @@ -33,41 +40,19 @@ impl State { self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); } - /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success. + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. #[inline(always)] - pub fn run_enqueue(&self) -> bool { - self.state - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { - // If already scheduled, or if not started, - if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { - None - } else { - // Mark it as scheduled - Some(state | STATE_RUN_QUEUED) - } - }) - .is_ok() + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel); + if prev & STATE_RUN_QUEUED == 0 { + locked(f); + } } /// Unmark the task as run-queued. Return whether the task is spawned. #[inline(always)] - pub fn run_dequeue(&self) -> bool { - let state = self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); - state & STATE_SPAWNED != 0 - } - - /// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before) - #[cfg(feature = "integrated-timers")] - #[inline(always)] - pub fn timer_enqueue(&self) -> bool { - let old_state = self.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); - old_state & STATE_TIMER_QUEUED == 0 - } - - /// Unmark the task as timer-queued. - #[cfg(feature = "integrated-timers")] - #[inline(always)] - pub fn timer_dequeue(&self) { - self.state.fetch_and(!STATE_TIMER_QUEUED, Ordering::AcqRel); + pub fn run_dequeue(&self) { + self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); } } diff --git a/embassy-executor/src/raw/state_atomics_arm.rs b/embassy-executor/src/raw/state_atomics_arm.rs index e4dfe5093..b743dcc2c 100644 --- a/embassy-executor/src/raw/state_atomics_arm.rs +++ b/embassy-executor/src/raw/state_atomics_arm.rs @@ -1,6 +1,15 @@ -use core::arch::asm; use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; +#[derive(Clone, Copy)] +pub(crate) struct Token(()); + +/// Creates a token and passes it to the closure. +/// +/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. +pub(crate) fn locked(f: impl FnOnce(Token) -> R) -> R { + f(Token(())) +} + // Must be kept in sync with the layout of `State`! pub(crate) const STATE_SPAWNED: u32 = 1 << 0; pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8; @@ -11,9 +20,8 @@ pub(crate) struct State { spawned: AtomicBool, /// Task is in the executor run queue run_queued: AtomicBool, - /// Task is in the executor timer queue - timer_queued: AtomicBool, pad: AtomicBool, + pad2: AtomicBool, } impl State { @@ -21,8 +29,8 @@ impl State { Self { spawned: AtomicBool::new(false), run_queued: AtomicBool::new(false), - timer_queued: AtomicBool::new(false), pad: AtomicBool::new(false), + pad2: AtomicBool::new(false), } } @@ -54,50 +62,22 @@ impl State { self.spawned.store(false, Ordering::Relaxed); } - /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success. + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. #[inline(always)] - pub fn run_enqueue(&self) -> bool { - unsafe { - loop { - let state: u32; - asm!("ldrex {}, [{}]", out(reg) state, in(reg) self, options(nostack)); + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + let old = self.run_queued.swap(true, Ordering::AcqRel); - if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { - asm!("clrex", options(nomem, nostack)); - return false; - } - - let outcome: usize; - let new_state = state | STATE_RUN_QUEUED; - asm!("strex {}, {}, [{}]", out(reg) outcome, in(reg) new_state, in(reg) self, options(nostack)); - if outcome == 0 { - return true; - } - } + if !old { + locked(f); } } /// Unmark the task as run-queued. Return whether the task is spawned. #[inline(always)] - pub fn run_dequeue(&self) -> bool { + pub fn run_dequeue(&self) { compiler_fence(Ordering::Release); - let r = self.spawned.load(Ordering::Relaxed); self.run_queued.store(false, Ordering::Relaxed); - r - } - - /// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before) - #[cfg(feature = "integrated-timers")] - #[inline(always)] - pub fn timer_enqueue(&self) -> bool { - !self.timer_queued.swap(true, Ordering::Relaxed) - } - - /// Unmark the task as timer-queued. - #[cfg(feature = "integrated-timers")] - #[inline(always)] - pub fn timer_dequeue(&self) { - self.timer_queued.store(false, Ordering::Relaxed); } } diff --git a/embassy-executor/src/raw/state_critical_section.rs b/embassy-executor/src/raw/state_critical_section.rs index c3cc1b0b7..ec08f2f58 100644 --- a/embassy-executor/src/raw/state_critical_section.rs +++ b/embassy-executor/src/raw/state_critical_section.rs @@ -1,17 +1,15 @@ use core::cell::Cell; -use critical_section::Mutex; +pub(crate) use critical_section::{with as locked, CriticalSection as Token}; +use critical_section::{CriticalSection, Mutex}; /// Task is spawned (has a future) -pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +pub(crate) const STATE_SPAWNED: u8 = 1 << 0; /// Task is in the executor run queue -pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; -/// Task is in the executor timer queue -#[cfg(feature = "integrated-timers")] -pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; +pub(crate) const STATE_RUN_QUEUED: u8 = 1 << 1; pub(crate) struct State { - state: Mutex>, + state: Mutex>, } impl State { @@ -21,14 +19,16 @@ impl State { } } - fn update(&self, f: impl FnOnce(&mut u32) -> R) -> R { - critical_section::with(|cs| { - let s = self.state.borrow(cs); - let mut val = s.get(); - let r = f(&mut val); - s.set(val); - r - }) + fn update(&self, f: impl FnOnce(&mut u8) -> R) -> R { + critical_section::with(|cs| self.update_with_cs(cs, f)) + } + + fn update_with_cs(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u8) -> R) -> R { + let s = self.state.borrow(cs); + let mut val = s.get(); + let r = f(&mut val); + s.set(val); + r } /// If task is idle, mark it as spawned + run_queued and return true. @@ -50,44 +50,24 @@ impl State { self.update(|s| *s &= !STATE_SPAWNED); } - /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success. + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. #[inline(always)] - pub fn run_enqueue(&self) -> bool { - self.update(|s| { - if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) { - false - } else { + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + critical_section::with(|cs| { + if self.update_with_cs(cs, |s| { + let ok = *s & STATE_RUN_QUEUED == 0; *s |= STATE_RUN_QUEUED; - true + ok + }) { + f(cs); } - }) + }); } /// Unmark the task as run-queued. Return whether the task is spawned. #[inline(always)] - pub fn run_dequeue(&self) -> bool { - self.update(|s| { - let ok = *s & STATE_SPAWNED != 0; - *s &= !STATE_RUN_QUEUED; - ok - }) - } - - /// Mark the task as timer-queued. Return whether it was newly queued (i.e. not queued before) - #[cfg(feature = "integrated-timers")] - #[inline(always)] - pub fn timer_enqueue(&self) -> bool { - self.update(|s| { - let ok = *s & STATE_TIMER_QUEUED == 0; - *s |= STATE_TIMER_QUEUED; - ok - }) - } - - /// Unmark the task as timer-queued. - #[cfg(feature = "integrated-timers")] - #[inline(always)] - pub fn timer_dequeue(&self) { - self.update(|s| *s &= !STATE_TIMER_QUEUED); + pub fn run_dequeue(&self, cs: CriticalSection<'_>) { + self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED) } } diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs index 94a5f340b..e52453be4 100644 --- a/embassy-executor/src/raw/timer_queue.rs +++ b/embassy-executor/src/raw/timer_queue.rs @@ -1,76 +1,73 @@ -use core::cmp::min; +//! Timer queue operations. + +use core::cell::Cell; use super::TaskRef; -use crate::raw::util::SyncUnsafeCell; -pub(crate) struct TimerQueueItem { - next: SyncUnsafeCell>, +#[cfg(feature = "_timer-item-payload")] +macro_rules! define_opaque { + ($size:tt) => { + /// An opaque data type. + #[repr(align($size))] + pub struct OpaqueData { + data: [u8; $size], + } + + impl OpaqueData { + const fn new() -> Self { + Self { data: [0; $size] } + } + + /// Access the data as a reference to a type `T`. + /// + /// Safety: + /// + /// The caller must ensure that the size of the type `T` is less than, or equal to + /// the size of the payload, and must ensure that the alignment of the type `T` is + /// less than, or equal to the alignment of the payload. + /// + /// The type must be valid when zero-initialized. + pub unsafe fn as_ref(&self) -> &T { + &*(self.data.as_ptr() as *const T) + } + } + }; } +#[cfg(feature = "timer-item-payload-size-1")] +define_opaque!(1); +#[cfg(feature = "timer-item-payload-size-2")] +define_opaque!(2); +#[cfg(feature = "timer-item-payload-size-4")] +define_opaque!(4); +#[cfg(feature = "timer-item-payload-size-8")] +define_opaque!(8); + +/// An item in the timer queue. +pub struct TimerQueueItem { + /// The next item in the queue. + /// + /// If this field contains `Some`, the item is in the queue. The last item in the queue has a + /// value of `Some(dangling_pointer)` + pub next: Cell>, + + /// The time at which this item expires. + pub expires_at: Cell, + + /// Some implementation-defined, zero-initialized piece of data. + #[cfg(feature = "_timer-item-payload")] + pub payload: OpaqueData, +} + +unsafe impl Sync for TimerQueueItem {} + impl TimerQueueItem { - pub const fn new() -> Self { + pub(crate) const fn new() -> Self { Self { - next: SyncUnsafeCell::new(None), - } - } -} - -pub(crate) struct TimerQueue { - head: SyncUnsafeCell>, -} - -impl TimerQueue { - pub const fn new() -> Self { - Self { - head: SyncUnsafeCell::new(None), - } - } - - pub(crate) unsafe fn update(&self, p: TaskRef) { - let task = p.header(); - if task.expires_at.get() != u64::MAX { - if task.state.timer_enqueue() { - task.timer_queue_item.next.set(self.head.get()); - self.head.set(Some(p)); - } - } - } - - pub(crate) unsafe fn next_expiration(&self) -> u64 { - let mut res = u64::MAX; - self.retain(|p| { - let task = p.header(); - let expires = task.expires_at.get(); - res = min(res, expires); - expires != u64::MAX - }); - res - } - - pub(crate) unsafe fn dequeue_expired(&self, now: u64, on_task: impl Fn(TaskRef)) { - self.retain(|p| { - let task = p.header(); - if task.expires_at.get() <= now { - on_task(p); - false - } else { - true - } - }); - } - - pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { - let mut prev = &self.head; - while let Some(p) = prev.get() { - let task = p.header(); - if f(p) { - // Skip to next - prev = &task.timer_queue_item.next; - } else { - // Remove it - prev.set(task.timer_queue_item.next.get()); - task.state.timer_dequeue(); - } + next: Cell::new(None), + expires_at: Cell::new(0), + #[cfg(feature = "_timer-item-payload")] + payload: OpaqueData::new(), } } } diff --git a/embassy-executor/src/raw/trace.rs b/embassy-executor/src/raw/trace.rs new file mode 100644 index 000000000..6c9cfda25 --- /dev/null +++ b/embassy-executor/src/raw/trace.rs @@ -0,0 +1,412 @@ +//! # Tracing +//! +//! The `trace` feature enables a number of callbacks that can be used to track the +//! lifecycle of tasks and/or executors. +//! +//! Callbacks will have one or both of the following IDs passed to them: +//! +//! 1. A `task_id`, a `u32` value unique to a task for the duration of the time it is valid +//! 2. An `executor_id`, a `u32` value unique to an executor for the duration of the time it is +//! valid +//! +//! Today, both `task_id` and `executor_id` are u32s containing the least significant 32 bits of +//! the address of the task or executor, however this is NOT a stable guarantee, and MAY change +//! at any time. +//! +//! IDs are only guaranteed to be unique for the duration of time the item is valid. If a task +//! ends, and is re-spawned, it MAY or MAY NOT have the same ID. For tasks, this valid time is defined +//! as the time between `_embassy_trace_task_new` and `_embassy_trace_task_end` for a given task. +//! For executors, this time is not defined, but is often "forever" for practical embedded +//! programs. +//! +//! Callbacks can be used by enabling the `trace` feature, and providing implementations of the +//! `extern "Rust"` functions below. All callbacks must be implemented. +//! +//! ## Task Tracing lifecycle +//! +//! ```text +//! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ +//! │(1) │ +//! │ │ +//! ╔════▼════╗ (2) ┌─────────┐ (3) ┌─────────┐ │ +//! │ ║ SPAWNED ║────▶│ WAITING │────▶│ RUNNING │ +//! ╚═════════╝ └─────────┘ └─────────┘ │ +//! │ ▲ ▲ │ │ │ +//! │ (4) │ │(6) │ +//! │ │(7) └ ─ ─ ┘ │ │ +//! │ │ │ │ +//! │ ┌──────┐ (5) │ │ ┌─────┐ +//! │ IDLE │◀────────────────┘ └─▶│ END │ │ +//! │ └──────┘ └─────┘ +//! ┌──────────────────────┐ │ +//! └ ┤ Task Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ +//! └──────────────────────┘ +//! ``` +//! +//! 1. A task is spawned, `_embassy_trace_task_new` is called +//! 2. A task is enqueued for the first time, `_embassy_trace_task_ready_begin` is called +//! 3. A task is polled, `_embassy_trace_task_exec_begin` is called +//! 4. WHILE a task is polled, the task is re-awoken, and `_embassy_trace_task_ready_begin` is +//! called. The task does not IMMEDIATELY move state, until polling is complete and the +//! RUNNING state is existed. `_embassy_trace_task_exec_end` is called when polling is +//! complete, marking the transition to WAITING +//! 5. Polling is complete, `_embassy_trace_task_exec_end` is called +//! 6. The task has completed, and `_embassy_trace_task_end` is called +//! 7. A task is awoken, `_embassy_trace_task_ready_begin` is called +//! +//! ## Executor Tracing lifecycle +//! +//! ```text +//! ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ +//! │(1) │ +//! │ │ +//! ╔═══▼══╗ (2) ┌────────────┐ (3) ┌─────────┐ │ +//! │ ║ IDLE ║──────────▶│ SCHEDULING │──────▶│ POLLING │ +//! ╚══════╝ └────────────┘ └─────────┘ │ +//! │ ▲ │ ▲ │ +//! │ (5) │ │ (4) │ │ +//! │ └──────────────┘ └────────────┘ +//! ┌──────────────────────────┐ │ +//! └ ┤ Executor Trace Lifecycle │─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ +//! └──────────────────────────┘ +//! ``` +//! +//! 1. The executor is started (no associated trace) +//! 2. A task on this executor is awoken. `_embassy_trace_task_ready_begin` is called +//! when this occurs, and `_embassy_trace_poll_start` is called when the executor +//! actually begins running +//! 3. The executor has decided a task to poll. `_embassy_trace_task_exec_begin` is called +//! 4. The executor finishes polling the task. `_embassy_trace_task_exec_end` is called +//! 5. The executor has finished polling tasks. `_embassy_trace_executor_idle` is called + +#![allow(unused)] + +use core::cell::UnsafeCell; +use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; + +use rtos_trace::TaskInfo; + +use crate::raw::{SyncExecutor, TaskHeader, TaskRef}; +use crate::spawner::{SpawnError, SpawnToken, Spawner}; + +/// Global task tracker instance +/// +/// This static provides access to the global task tracker which maintains +/// a list of all tasks in the system. It's automatically updated by the +/// task lifecycle hooks in the trace module. +pub static TASK_TRACKER: TaskTracker = TaskTracker::new(); + +/// A thread-safe tracker for all tasks in the system +/// +/// This struct uses an intrusive linked list approach to track all tasks +/// without additional memory allocations. It maintains a global list of +/// tasks that can be traversed to find all currently existing tasks. +pub struct TaskTracker { + head: AtomicPtr, +} + +impl TaskTracker { + /// Creates a new empty task tracker + /// + /// Initializes a tracker with no tasks in its list. + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(core::ptr::null_mut()), + } + } + + /// Adds a task to the tracker + /// + /// This method inserts a task at the head of the intrusive linked list. + /// The operation is thread-safe and lock-free, using atomic operations + /// to ensure consistency even when called from different contexts. + /// + /// # Arguments + /// * `task` - The task reference to add to the tracker + pub fn add(&self, task: TaskRef) { + let task_ptr = task.as_ptr() as *mut TaskHeader; + + loop { + let current_head = self.head.load(Ordering::Acquire); + unsafe { + (*task_ptr).all_tasks_next.store(current_head, Ordering::Relaxed); + } + + if self + .head + .compare_exchange(current_head, task_ptr, Ordering::Release, Ordering::Relaxed) + .is_ok() + { + break; + } + } + } + + /// Performs an operation on each task in the tracker + /// + /// This method traverses the entire list of tasks and calls the provided + /// function for each task. This allows inspecting or processing all tasks + /// in the system without modifying the tracker's structure. + /// + /// # Arguments + /// * `f` - A function to call for each task in the tracker + pub fn for_each(&self, mut f: F) + where + F: FnMut(TaskRef), + { + let mut current = self.head.load(Ordering::Acquire); + while !current.is_null() { + let task = unsafe { TaskRef::from_ptr(current) }; + f(task); + + current = unsafe { (*current).all_tasks_next.load(Ordering::Acquire) }; + } + } +} + +/// Extension trait for `TaskRef` that provides tracing functionality. +/// +/// This trait is only available when the `trace` feature is enabled. +/// It extends `TaskRef` with methods for accessing and modifying task identifiers +/// and names, which are useful for debugging, logging, and performance analysis. +pub trait TaskRefTrace { + /// Get the name for a task + fn name(&self) -> Option<&'static str>; + + /// Set the name for a task + fn set_name(&self, name: Option<&'static str>); + + /// Get the ID for a task + fn id(&self) -> u32; + + /// Set the ID for a task + fn set_id(&self, id: u32); +} + +impl TaskRefTrace for TaskRef { + fn name(&self) -> Option<&'static str> { + self.header().name + } + + fn set_name(&self, name: Option<&'static str>) { + unsafe { + let header_ptr = self.ptr.as_ptr() as *mut TaskHeader; + (*header_ptr).name = name; + } + } + + fn id(&self) -> u32 { + self.header().id + } + + fn set_id(&self, id: u32) { + unsafe { + let header_ptr = self.ptr.as_ptr() as *mut TaskHeader; + (*header_ptr).id = id; + } + } +} + +#[cfg(not(feature = "rtos-trace"))] +extern "Rust" { + /// This callback is called when the executor begins polling. This will always + /// be paired with a later call to `_embassy_trace_executor_idle`. + /// + /// This marks the EXECUTOR state transition from IDLE -> SCHEDULING. + fn _embassy_trace_poll_start(executor_id: u32); + + /// This callback is called AFTER a task is initialized/allocated, and BEFORE + /// it is enqueued to run for the first time. If the task ends (and does not + /// loop "forever"), there will be a matching call to `_embassy_trace_task_end`. + /// + /// Tasks start life in the SPAWNED state. + fn _embassy_trace_task_new(executor_id: u32, task_id: u32); + + /// This callback is called AFTER a task is destructed/freed. This will always + /// have a prior matching call to `_embassy_trace_task_new`. + fn _embassy_trace_task_end(executor_id: u32, task_id: u32); + + /// This callback is called AFTER a task has been dequeued from the runqueue, + /// and BEFORE the task is polled. There will always be a matching call to + /// `_embassy_trace_task_exec_end`. + /// + /// This marks the TASK state transition from WAITING -> RUNNING + /// This marks the EXECUTOR state transition from SCHEDULING -> POLLING + fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32); + + /// This callback is called AFTER a task has completed polling. There will + /// always be a matching call to `_embassy_trace_task_exec_begin`. + /// + /// This marks the TASK state transition from either: + /// * RUNNING -> IDLE - if there were no `_embassy_trace_task_ready_begin` events + /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task + /// * RUNNING -> WAITING - if there WAS a `_embassy_trace_task_ready_begin` event + /// for this task since the last `_embassy_trace_task_exec_begin` for THIS task + /// + /// This marks the EXECUTOR state transition from POLLING -> SCHEDULING + fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32); + + /// This callback is called AFTER the waker for a task is awoken, and BEFORE it + /// is added to the run queue. + /// + /// If the given task is currently RUNNING, this marks no state change, BUT the + /// RUNNING task will then move to the WAITING stage when polling is complete. + /// + /// If the given task is currently IDLE, this marks the TASK state transition + /// from IDLE -> WAITING. + /// + /// NOTE: This may be called from an interrupt, outside the context of the current + /// task or executor. + fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32); + + /// This callback is called AFTER all dequeued tasks in a single call to poll + /// have been processed. This will always be paired with a call to + /// `_embassy_trace_executor_idle`. + /// + /// This marks the EXECUTOR state transition from SCHEDULING -> IDLE + fn _embassy_trace_executor_idle(executor_id: u32); +} + +#[inline] +pub(crate) fn poll_start(executor: &SyncExecutor) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_poll_start(executor as *const _ as u32) + } +} + +#[inline] +pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32) + } + + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_new(task.as_ptr() as u32); + + #[cfg(feature = "rtos-trace")] + TASK_TRACKER.add(*task); +} + +#[inline] +pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_end(executor as u32, task.as_ptr() as u32) + } +} + +#[inline] +pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_ready_begin(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_exec_begin(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_exec_end(); +} + +#[inline] +pub(crate) fn executor_idle(executor: &SyncExecutor) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_executor_idle(executor as *const _ as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::system_idle(); +} + +/// Returns an iterator over all active tasks in the system +/// +/// This function provides a convenient way to iterate over all tasks +/// that are currently tracked in the system. The returned iterator +/// yields each task in the global task tracker. +/// +/// # Returns +/// An iterator that yields `TaskRef` items for each task +fn get_all_active_tasks() -> impl Iterator + 'static { + struct TaskIterator<'a> { + tracker: &'a TaskTracker, + current: *mut TaskHeader, + } + + impl<'a> Iterator for TaskIterator<'a> { + type Item = TaskRef; + + fn next(&mut self) -> Option { + if self.current.is_null() { + return None; + } + + let task = unsafe { TaskRef::from_ptr(self.current) }; + self.current = unsafe { (*self.current).all_tasks_next.load(Ordering::Acquire) }; + + Some(task) + } + } + + TaskIterator { + tracker: &TASK_TRACKER, + current: TASK_TRACKER.head.load(Ordering::Acquire), + } +} + +/// Perform an action on each active task +fn with_all_active_tasks(f: F) +where + F: FnMut(TaskRef), +{ + TASK_TRACKER.for_each(f); +} + +#[cfg(feature = "rtos-trace")] +impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { + fn task_list() { + with_all_active_tasks(|task| { + let name = task.name().unwrap_or("unnamed task\0"); + let info = rtos_trace::TaskInfo { + name, + priority: 0, + stack_base: 0, + stack_size: 0, + }; + rtos_trace::trace::task_send_info(task.id(), info); + }); + } + fn time() -> u64 { + const fn gcd(a: u64, b: u64) -> u64 { + if b == 0 { + a + } else { + gcd(b, a % b) + } + } + + 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(feature = "rtos-trace")] +rtos_trace::global_os_callbacks! {SyncExecutor} diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs index 8d3910a25..d0d7b003d 100644 --- a/embassy-executor/src/raw/waker.rs +++ b/embassy-executor/src/raw/waker.rs @@ -26,38 +26,17 @@ pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { /// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps /// avoid dynamic dispatch. /// -/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// You can use the returned task pointer to wake the task with [`wake_task`]. /// /// # Panics /// /// Panics if the waker is not created by the Embassy executor. pub fn task_from_waker(waker: &Waker) -> TaskRef { - let (vtable, data) = { - #[cfg(not(feature = "nightly"))] - { - struct WakerHack { - data: *const (), - vtable: &'static RawWakerVTable, - } - - // safety: OK because WakerHack has the same layout as Waker. - // This is not really guaranteed because the structs are `repr(Rust)`, it is - // indeed the case in the current implementation. - // TODO use waker_getters when stable. https://github.com/rust-lang/rust/issues/96992 - let hack: &WakerHack = unsafe { core::mem::transmute(waker) }; - (hack.vtable, hack.data) - } - - #[cfg(feature = "nightly")] - { - let raw_waker = waker.as_raw(); - (raw_waker.vtable(), raw_waker.data()) - } - }; - - if vtable != &VTABLE { + // make sure to compare vtable addresses. Doing `==` on the references + // will compare the contents, which is slower. + if waker.vtable() as *const _ != &VTABLE as *const _ { panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") } // safety: our wakers are always created with `TaskRef::as_ptr` - unsafe { TaskRef::from_ptr(data as *const TaskHeader) } + unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) } } diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 271606244..522d97db3 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -1,9 +1,12 @@ -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::mem; +use core::sync::atomic::Ordering; use core::task::Poll; use super::raw; +#[cfg(feature = "trace")] +use crate::raw::trace::TaskRefTrace; /// Token to spawn a newly-created task in an executor. /// @@ -21,7 +24,7 @@ use super::raw; /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] pub struct SpawnToken { - raw_task: Option, + pub(crate) raw_task: Option, phantom: PhantomData<*mut S>, } @@ -33,6 +36,15 @@ impl SpawnToken { } } + /// Returns the task id if available, otherwise 0 + /// This can be used in combination with rtos-trace to match task names with id's + pub fn id(&self) -> u32 { + match self.raw_task { + None => 0, + Some(t) => t.as_ptr() as u32, + } + } + /// Return a SpawnToken that represents a failed spawn. pub fn new_failed() -> Self { Self { @@ -50,8 +62,7 @@ impl Drop for SpawnToken { } /// Error returned when spawning a task. -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Copy, Clone)] pub enum SpawnError { /// Too many instances of this task are already running. /// @@ -61,6 +72,31 @@ pub enum SpawnError { Busy, } +impl core::fmt::Debug for SpawnError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self, f) + } +} + +impl core::fmt::Display for SpawnError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + SpawnError::Busy => write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."), + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for SpawnError { + fn format(&self, f: defmt::Formatter) { + match self { + SpawnError::Busy => defmt::write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."), + } + } +} + +impl core::error::Error for SpawnError {} + /// Handle to spawn tasks into an executor. /// /// This Spawner can spawn any task (Send and non-Send ones), but it can @@ -69,7 +105,7 @@ pub enum SpawnError { /// If you want to spawn tasks from another thread, use [SendSpawner]. #[derive(Copy, Clone)] pub struct Spawner { - executor: &'static raw::Executor, + pub(crate) executor: &'static raw::Executor, not_send: PhantomData<*mut ()>, } @@ -89,14 +125,19 @@ impl Spawner { /// # Panics /// /// Panics if the current executor is not an Embassy executor. - pub async fn for_current_executor() -> Self { + pub fn for_current_executor() -> impl Future { poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + let executor = unsafe { + task.header() + .executor + .load(Ordering::Relaxed) + .as_ref() + .unwrap_unchecked() + }; let executor = unsafe { raw::Executor::wrap(executor) }; Poll::Ready(Self::new(executor)) }) - .await } /// Spawn a task into an executor. @@ -134,6 +175,58 @@ impl Spawner { pub fn make_send(&self) -> SendSpawner { SendSpawner::new(&self.executor.inner) } + + /// Return the unique ID of this Spawner's Executor. + pub fn executor_id(&self) -> usize { + self.executor.id() + } +} + +/// Extension trait adding tracing capabilities to the Spawner +/// +/// This trait provides an additional method to spawn tasks with an associated name, +/// which can be useful for debugging and tracing purposes. +pub trait SpawnerTraceExt { + /// Spawns a new task with a specified name. + /// + /// # Arguments + /// * `name` - Static string name to associate with the task + /// * `token` - Token representing the task to spawn + /// + /// # Returns + /// Result indicating whether the spawn was successful + fn spawn_named(&self, name: &'static str, token: SpawnToken) -> Result<(), SpawnError>; +} + +/// Implementation of the SpawnerTraceExt trait for Spawner when trace is enabled +#[cfg(feature = "trace")] +impl SpawnerTraceExt for Spawner { + fn spawn_named(&self, name: &'static str, token: SpawnToken) -> Result<(), SpawnError> { + let task = token.raw_task; + core::mem::forget(token); + + match task { + Some(task) => { + // Set the name and ID when trace is enabled + task.set_name(Some(name)); + let task_id = task.as_ptr() as u32; + task.set_id(task_id); + + unsafe { self.executor.spawn(task) }; + Ok(()) + } + None => Err(SpawnError::Busy), + } + } +} + +/// Implementation of the SpawnerTraceExt trait for Spawner when trace is disabled +#[cfg(not(feature = "trace"))] +impl SpawnerTraceExt for Spawner { + fn spawn_named(&self, _name: &'static str, token: SpawnToken) -> Result<(), SpawnError> { + // When trace is disabled, just forward to regular spawn and ignore the name + self.spawn(token) + } } /// Handle to spawn tasks into an executor from any thread. @@ -161,13 +254,18 @@ impl SendSpawner { /// # Panics /// /// Panics if the current executor is not an Embassy executor. - pub async fn for_current_executor() -> Self { + pub fn for_current_executor() -> impl Future { poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + let executor = unsafe { + task.header() + .executor + .load(Ordering::Relaxed) + .as_ref() + .unwrap_unchecked() + }; Poll::Ready(Self::new(executor)) }) - .await } /// Spawn a task into an executor. diff --git a/embassy-executor/tests/test.rs b/embassy-executor/tests/test.rs index 348cc7dc4..78c49c071 100644 --- a/embassy-executor/tests/test.rs +++ b/embassy-executor/tests/test.rs @@ -40,6 +40,7 @@ fn setup() -> (&'static Executor, Trace) { let trace = Trace::new(); let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut (); let executor = &*Box::leak(Box::new(Executor::new(context))); + (executor, trace) } @@ -136,6 +137,139 @@ fn executor_task_self_wake_twice() { ) } +#[test] +fn waking_after_completion_does_not_poll() { + use embassy_sync::waitqueue::AtomicWaker; + + #[task] + async fn task1(trace: Trace, waker: &'static AtomicWaker) { + poll_fn(|cx| { + trace.push("poll task1"); + waker.register(cx.waker()); + Poll::Ready(()) + }) + .await + } + + let waker = Box::leak(Box::new(AtomicWaker::new())); + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + waker.wake(); + unsafe { executor.poll() }; + + // Exited task may be waken but is not polled + waker.wake(); + waker.wake(); + unsafe { executor.poll() }; // Clears running status + + // Can respawn waken-but-dead task + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // + "pend", // manual wake, gets cleared by poll + "pend", // manual wake, single pend for two wakes + "pend", // respawning a task pends the executor + "poll task1", // + ] + ) +} + +#[test] +fn waking_with_old_waker_after_respawn() { + use embassy_sync::waitqueue::AtomicWaker; + + async fn yield_now(trace: Trace) { + let mut yielded = false; + poll_fn(|cx| { + if yielded { + Poll::Ready(()) + } else { + trace.push("yield_now"); + yielded = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + .await + } + + #[task] + async fn task1(trace: Trace, waker: &'static AtomicWaker) { + yield_now(trace.clone()).await; + poll_fn(|cx| { + trace.push("poll task1"); + waker.register(cx.waker()); + Poll::Ready(()) + }) + .await; + } + + let waker = Box::leak(Box::new(AtomicWaker::new())); + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; // progress to registering the waker + waker.wake(); + unsafe { executor.poll() }; + // Task has exited + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // yield_now wakes the task + "poll task1", // + "pend", // task self-wakes + ] + ); + + // Can respawn task on another executor + let (other_executor, other_trace) = setup(); + other_executor + .spawner() + .spawn(task1(other_trace.clone(), waker)) + .unwrap(); + + unsafe { other_executor.poll() }; // just run to the yield_now + waker.wake(); // trigger old waker registration + unsafe { executor.poll() }; + unsafe { other_executor.poll() }; + + // First executor's trace has not changed + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // yield_now wakes the task + "poll task1", // + "pend", // task self-wakes + ] + ); + + assert_eq!( + other_trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // manual wake, gets cleared by poll + "poll task1", // + ] + ); +} + #[test] fn executor_task_cfg_args() { // simulate cfg'ing away argument c diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs new file mode 100644 index 000000000..278a4b903 --- /dev/null +++ b/embassy-executor/tests/ui.rs @@ -0,0 +1,24 @@ +#[cfg(not(miri))] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/abi.rs"); + t.compile_fail("tests/ui/bad_return.rs"); + t.compile_fail("tests/ui/generics.rs"); + t.compile_fail("tests/ui/impl_trait_nested.rs"); + t.compile_fail("tests/ui/impl_trait.rs"); + t.compile_fail("tests/ui/impl_trait_static.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon.rs"); + t.compile_fail("tests/ui/nonstatic_ref_elided.rs"); + t.compile_fail("tests/ui/nonstatic_ref_generic.rs"); + t.compile_fail("tests/ui/nonstatic_struct_anon.rs"); + #[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly. + t.compile_fail("tests/ui/nonstatic_struct_elided.rs"); + t.compile_fail("tests/ui/nonstatic_struct_generic.rs"); + t.compile_fail("tests/ui/not_async.rs"); + t.compile_fail("tests/ui/self_ref.rs"); + t.compile_fail("tests/ui/self.rs"); + t.compile_fail("tests/ui/type_error.rs"); + t.compile_fail("tests/ui/where_clause.rs"); +} diff --git a/embassy-executor/tests/ui/abi.rs b/embassy-executor/tests/ui/abi.rs new file mode 100644 index 000000000..fd52f7e41 --- /dev/null +++ b/embassy-executor/tests/ui/abi.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async extern "C" fn task() {} + +fn main() {} diff --git a/embassy-executor/tests/ui/abi.stderr b/embassy-executor/tests/ui/abi.stderr new file mode 100644 index 000000000..e264e371a --- /dev/null +++ b/embassy-executor/tests/ui/abi.stderr @@ -0,0 +1,5 @@ +error: task functions must not have an ABI qualifier + --> tests/ui/abi.rs:6:1 + | +6 | async extern "C" fn task() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/bad_return.rs b/embassy-executor/tests/ui/bad_return.rs new file mode 100644 index 000000000..f09a5205b --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.rs @@ -0,0 +1,10 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() -> u32 { + 5 +} + +fn main() {} diff --git a/embassy-executor/tests/ui/bad_return.stderr b/embassy-executor/tests/ui/bad_return.stderr new file mode 100644 index 000000000..e9d94dff8 --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.stderr @@ -0,0 +1,5 @@ +error: task functions must either not return a value, return `()` or return `!` + --> tests/ui/bad_return.rs:6:1 + | +6 | async fn task() -> u32 { + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/generics.rs b/embassy-executor/tests/ui/generics.rs new file mode 100644 index 000000000..b83123bb1 --- /dev/null +++ b/embassy-executor/tests/ui/generics.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_t: T) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/generics.stderr b/embassy-executor/tests/ui/generics.stderr new file mode 100644 index 000000000..197719a7b --- /dev/null +++ b/embassy-executor/tests/ui/generics.stderr @@ -0,0 +1,5 @@ +error: task functions must not be generic + --> tests/ui/generics.rs:6:1 + | +6 | async fn task(_t: T) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait.rs b/embassy-executor/tests/ui/impl_trait.rs new file mode 100644 index 000000000..a21402aa0 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait.stderr b/embassy-executor/tests/ui/impl_trait.stderr new file mode 100644 index 000000000..099b1828f --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait.rs:4:18 + | +4 | async fn foo(_x: impl Sized) {} + | ^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_nested.rs b/embassy-executor/tests/ui/impl_trait_nested.rs new file mode 100644 index 000000000..07442b8fa --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo(T); + +#[embassy_executor::task] +async fn foo(_x: Foo) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait_nested.stderr b/embassy-executor/tests/ui/impl_trait_nested.stderr new file mode 100644 index 000000000..39592f958 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_nested.rs:6:22 + | +6 | async fn foo(_x: Foo) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_static.rs b/embassy-executor/tests/ui/impl_trait_static.rs new file mode 100644 index 000000000..272470f98 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized + 'static) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait_static.stderr b/embassy-executor/tests/ui/impl_trait_static.stderr new file mode 100644 index 000000000..0032a20c9 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_static.rs:4:18 + | +4 | async fn foo(_x: impl Sized + 'static) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.rs b/embassy-executor/tests/ui/nonstatic_ref_anon.rs new file mode 100644 index 000000000..417c360a1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'_ u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr new file mode 100644 index 000000000..0544de843 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon.rs:4:19 + | +4 | async fn foo(_x: &'_ u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs new file mode 100644 index 000000000..175ebccc1 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'static &'_ u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr new file mode 100644 index 000000000..79f262e6b --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon_nested.rs:4:28 + | +4 | async fn foo(_x: &'static &'_ u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.rs b/embassy-executor/tests/ui/nonstatic_ref_elided.rs new file mode 100644 index 000000000..cf49ad709 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.stderr b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr new file mode 100644 index 000000000..7e2b9eb7c --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_elided.rs:4:18 + | +4 | async fn foo(_x: &u32) {} + | ^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.rs b/embassy-executor/tests/ui/nonstatic_ref_generic.rs new file mode 100644 index 000000000..3f8a26cf8 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo<'a>(_x: &'a u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.stderr b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr new file mode 100644 index 000000000..af8491ad7 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_ref_generic.rs:4:1 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_generic.rs:4:23 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.rs b/embassy-executor/tests/ui/nonstatic_struct_anon.rs new file mode 100644 index 000000000..ba95d1459 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo<'_>) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.stderr b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr new file mode 100644 index 000000000..5df2a6e06 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_anon.rs:6:23 + | +6 | async fn task(_x: Foo<'_>) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.rs b/embassy-executor/tests/ui/nonstatic_struct_elided.rs new file mode 100644 index 000000000..4cfe2966a --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr new file mode 100644 index 000000000..099ef8b4e --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr @@ -0,0 +1,10 @@ +error[E0726]: implicit elided lifetime not allowed here + --> tests/ui/nonstatic_struct_elided.rs:6:19 + | +6 | async fn task(_x: Foo) {} + | ^^^ expected lifetime parameter + | +help: indicate the anonymous lifetime + | +6 | async fn task(_x: Foo<'_>) {} + | ++++ diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.rs b/embassy-executor/tests/ui/nonstatic_struct_generic.rs new file mode 100644 index 000000000..ec3d908f6 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task<'a>(_x: Foo<'a>) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.stderr b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr new file mode 100644 index 000000000..61d5231bc --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_struct_generic.rs:6:1 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_generic.rs:6:27 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^ diff --git a/embassy-executor/tests/ui/not_async.rs b/embassy-executor/tests/ui/not_async.rs new file mode 100644 index 000000000..f3f7e9bd2 --- /dev/null +++ b/embassy-executor/tests/ui/not_async.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +fn task() {} + +fn main() {} diff --git a/embassy-executor/tests/ui/not_async.stderr b/embassy-executor/tests/ui/not_async.stderr new file mode 100644 index 000000000..27f040d9c --- /dev/null +++ b/embassy-executor/tests/ui/not_async.stderr @@ -0,0 +1,5 @@ +error: task functions must be async + --> tests/ui/not_async.rs:6:1 + | +6 | fn task() {} + | ^^^^^^^^^ diff --git a/embassy-executor/tests/ui/self.rs b/embassy-executor/tests/ui/self.rs new file mode 100644 index 000000000..f83a962d1 --- /dev/null +++ b/embassy-executor/tests/ui/self.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(self) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/self.stderr b/embassy-executor/tests/ui/self.stderr new file mode 100644 index 000000000..aaf031573 --- /dev/null +++ b/embassy-executor/tests/ui/self.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy-executor/tests/ui/self_ref.rs b/embassy-executor/tests/ui/self_ref.rs new file mode 100644 index 000000000..5e49bba5e --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(&mut self) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/self_ref.stderr b/embassy-executor/tests/ui/self_ref.stderr new file mode 100644 index 000000000..dd2052977 --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy-executor/tests/ui/type_error.rs b/embassy-executor/tests/ui/type_error.rs new file mode 100644 index 000000000..1734bc6c4 --- /dev/null +++ b/embassy-executor/tests/ui/type_error.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn task() { + 5 +} + +fn main() {} diff --git a/embassy-executor/tests/ui/type_error.stderr b/embassy-executor/tests/ui/type_error.stderr new file mode 100644 index 000000000..bce315811 --- /dev/null +++ b/embassy-executor/tests/ui/type_error.stderr @@ -0,0 +1,7 @@ +error[E0308]: mismatched types + --> tests/ui/type_error.rs:5:5 + | +4 | async fn task() { + | - help: try adding a return type: `-> i32` +5 | 5 + | ^ expected `()`, found integer diff --git a/embassy-executor/tests/ui/where_clause.rs b/embassy-executor/tests/ui/where_clause.rs new file mode 100644 index 000000000..848d78149 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.rs @@ -0,0 +1,12 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() +where + (): Sized, +{ +} + +fn main() {} diff --git a/embassy-executor/tests/ui/where_clause.stderr b/embassy-executor/tests/ui/where_clause.stderr new file mode 100644 index 000000000..eba45af40 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.stderr @@ -0,0 +1,7 @@ +error: task functions must not have `where` clauses + --> tests/ui/where_clause.rs:6:1 + | +6 | / async fn task() +7 | | where +8 | | (): Sized, + | |______________^ diff --git a/embassy-futures/Cargo.toml b/embassy-futures/Cargo.toml index 47cefa56f..0deab0165 100644 --- a/embassy-futures/Cargo.toml +++ b/embassy-futures/Cargo.toml @@ -24,5 +24,5 @@ target = "thumbv7em-none-eabi" features = ["defmt"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 97a81a86d..014fee5d2 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -14,6 +14,18 @@ pub enum Either { Second(B), } +impl Either { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either::Second(_)) + } +} + /// Wait for one of two futures to complete. /// /// This function returns a new future which polls all the futures. @@ -73,6 +85,23 @@ pub enum Either3 { Third(C), } +impl Either3 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either3::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either3::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either3::Third(_)) + } +} + /// Same as [`select`], but with more futures. pub fn select3(a: A, b: B, c: C) -> Select3 where @@ -134,6 +163,28 @@ pub enum Either4 { Fourth(D), } +impl Either4 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either4::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either4::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either4::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either4::Fourth(_)) + } +} + /// Same as [`select`], but with more futures. pub fn select4(a: A, b: B, c: C, d: D) -> Select4 where @@ -188,6 +239,228 @@ where // ==================================================================== +/// Result for [`select5`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either5 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), +} + +impl Either5 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either5::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either5::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either5::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either5::Fourth(_)) + } + + /// Did the fifth future complete first? + pub fn is_fifth(&self) -> bool { + matches!(self, Either5::Fifth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select5(a: A, b: B, c: C, d: D, e: E) -> Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + Select5 { a, b, c, d, e } +} + +/// Future for the [`select5`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select5 { + a: A, + b: B, + c: C, + d: D, + e: E, +} + +impl Future for Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + type Output = Either5; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either5::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either5::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either5::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either5::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either5::Fifth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select6`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either6 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), + /// Sixth future finished first. + Sixth(F), +} + +impl Either6 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either6::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either6::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either6::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either6::Fourth(_)) + } + + /// Did the fifth future complete first? + pub fn is_fifth(&self) -> bool { + matches!(self, Either6::Fifth(_)) + } + + /// Did the sixth future complete first? + pub fn is_sixth(&self) -> bool { + matches!(self, Either6::Sixth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select6(a: A, b: B, c: C, d: D, e: E, f: F) -> Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + Select6 { a, b, c, d, e, f } +} + +/// Future for the [`select6`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select6 { + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, +} + +impl Future for Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + type Output = Either6; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + let f = unsafe { Pin::new_unchecked(&mut this.f) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either6::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either6::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either6::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either6::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either6::Fifth(x)); + } + if let Poll::Ready(x) = f.poll(cx) { + return Poll::Ready(Either6::Sixth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + /// Future for the [`select_array`] function. #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] @@ -237,7 +510,7 @@ impl Future for SelectArray { #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct SelectSlice<'a, Fut> { - inner: &'a mut [Fut], + inner: Pin<&'a mut [Fut]>, } /// Creates a new future which will select over a slice of futures. @@ -247,31 +520,26 @@ pub struct SelectSlice<'a, Fut> { /// future that was ready. /// /// If the slice is empty, the resulting future will be Pending forever. -pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> { +pub fn select_slice<'a, Fut: Future>(slice: Pin<&'a mut [Fut]>) -> SelectSlice<'a, Fut> { SelectSlice { inner: slice } } impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> { type Output = (Fut::Output, usize); - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move, - // its elements also cannot move. Therefore it is safe to access `inner` and pin - // references to the contained futures. - let item = unsafe { - self.get_unchecked_mut() - .inner - .iter_mut() - .enumerate() - .find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) { - Poll::Pending => None, - Poll::Ready(e) => Some((i, e)), - }) - }; - - match item { - Some((idx, res)) => Poll::Ready((res, idx)), - None => Poll::Pending, + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: refer to + // https://users.rust-lang.org/t/working-with-pinned-slices-are-there-any-structurally-pinning-vec-like-collection-types/50634/2 + #[inline(always)] + fn pin_iter(slice: Pin<&mut [T]>) -> impl Iterator> { + unsafe { slice.get_unchecked_mut().iter_mut().map(|v| Pin::new_unchecked(v)) } } + for (i, fut) in pin_iter(self.inner.as_mut()).enumerate() { + if let Poll::Ready(res) = fut.poll(cx) { + return Poll::Ready((res, i)); + } + } + + Poll::Pending } } diff --git a/embassy-hal-internal/Cargo.toml b/embassy-hal-internal/Cargo.toml index d5ca95ac2..cc360682e 100644 --- a/embassy-hal-internal/Cargo.toml +++ b/embassy-hal-internal/Cargo.toml @@ -28,7 +28,7 @@ prio-bits-8 = [] cortex-m = ["dep:cortex-m", "dep:critical-section"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } num-traits = { version = "0.2.14", default-features = false } diff --git a/embassy-hal-internal/src/lib.rs b/embassy-hal-internal/src/lib.rs index 89f20e993..7addb71e2 100644 --- a/embassy-hal-internal/src/lib.rs +++ b/embassy-hal-internal/src/lib.rs @@ -11,7 +11,7 @@ pub mod drop; mod macros; mod peripheral; pub mod ratio; -pub use peripheral::{Peripheral, PeripheralRef}; +pub use peripheral::{Peri, PeripheralType}; #[cfg(feature = "cortex-m")] pub mod interrupt; diff --git a/embassy-hal-internal/src/macros.rs b/embassy-hal-internal/src/macros.rs index 07cd89487..ce72ded5c 100644 --- a/embassy-hal-internal/src/macros.rs +++ b/embassy-hal-internal/src/macros.rs @@ -8,6 +8,8 @@ macro_rules! peripherals_definition { $(#[$cfg])? #[allow(non_camel_case_types)] #[doc = concat!(stringify!($name), " peripheral")] + #[derive(Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct $name { _private: () } $(#[$cfg])? @@ -18,8 +20,8 @@ macro_rules! peripherals_definition { /// /// You must ensure that you're only using one instance of this type at a time. #[inline] - pub unsafe fn steal() -> Self { - Self{ _private: ()} + pub unsafe fn steal() -> $crate::Peri<'static, Self> { + $crate::Peri::new_unchecked(Self{ _private: ()}) } } @@ -42,7 +44,7 @@ macro_rules! peripherals_struct { $( #[doc = concat!(stringify!($name), " peripheral")] $(#[$cfg])? - pub $name: peripherals::$name, + pub $name: $crate::Peri<'static, peripherals::$name>, )* } @@ -108,28 +110,26 @@ macro_rules! peripherals { }; } -/// Convenience converting into reference. -#[macro_export] -macro_rules! into_ref { - ($($name:ident),*) => { - $( - let mut $name = $name.into_ref(); - )* - } -} - /// Implement the peripheral trait. #[macro_export] macro_rules! impl_peripheral { - ($type:ident) => { - impl $crate::Peripheral for $type { - type P = $type; - - #[inline] - unsafe fn clone_unchecked(&self) -> Self::P { - #[allow(clippy::needless_update)] - $type { ..*self } + ($type:ident<$($T:ident $(: $bound:tt $(+ $others:tt )*)?),*>) => { + impl<$($T: $($bound $(+$others)*)?),*> Copy for $type <$($T),*> {} + impl<$($T: $($bound $(+$others)*)?),*> Clone for $type <$($T),*> { + fn clone(&self) -> Self { + *self } } + impl<$($T: $($bound $(+$others)*)?),*> PeripheralType for $type <$($T),*> {} + }; + + ($type:ident) => { + impl Copy for $type {} + impl Clone for $type { + fn clone(&self) -> Self { + *self + } + } + impl $crate::PeripheralType for $type {} }; } diff --git a/embassy-hal-internal/src/peripheral.rs b/embassy-hal-internal/src/peripheral.rs index 0b0f13338..b1868caf6 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. /// @@ -9,20 +9,28 @@ use core::ops::{Deref, DerefMut}; /// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete /// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte). /// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. -/// PeripheralRef stores a copy of `T` instead, so it's the same size. +/// Peripheral stores a copy of `T` instead, so it's the same size. /// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`, -/// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic -/// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes -/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization. -pub struct PeripheralRef<'a, T> { +/// the driver code would be monomorphized two times. With Peri, the driver is generic +/// over a lifetime only. `SPI4` becomes `Peri<'static, SPI4>`, and `&mut SPI4` becomes +/// `Peri<'a, SPI4>`. Lifetimes don't cause monomorphization. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Peri<'a, T: PeripheralType> { inner: T, _lifetime: PhantomData<&'a mut T>, } -impl<'a, T> PeripheralRef<'a, T> { - /// Create a new reference to a peripheral. +impl<'a, T: PeripheralType> Peri<'a, T> { + /// Create a new owned a peripheral. + /// + /// For use by HALs only. + /// + /// If you're an end user you shouldn't use this, you should use `steal()` + /// on the actual peripheral types instead. #[inline] - pub fn new(inner: T) -> Self { + #[doc(hidden)] + pub unsafe fn new_unchecked(inner: T) -> Self { Self { inner, _lifetime: PhantomData, @@ -38,46 +46,38 @@ impl<'a, T> PeripheralRef<'a, T> { /// create two SPI drivers on `SPI1`, because they will "fight" each other. /// /// You should strongly prefer using `reborrow()` instead. It returns a - /// `PeripheralRef` that borrows `self`, which allows the borrow checker + /// `Peri` that borrows `self`, which allows the borrow checker /// to enforce this at compile time. - pub unsafe fn clone_unchecked(&self) -> PeripheralRef<'a, T> - where - T: Peripheral

, - { - PeripheralRef::new(self.inner.clone_unchecked()) + pub unsafe fn clone_unchecked(&self) -> Peri<'a, T> { + Peri::new_unchecked(self.inner) } - /// Reborrow into a "child" PeripheralRef. + /// Reborrow into a "child" Peri. /// - /// `self` will stay borrowed until the child PeripheralRef is dropped. - pub fn reborrow(&mut self) -> PeripheralRef<'_, T> - where - T: Peripheral

, - { - // safety: we're returning the clone inside a new PeripheralRef that borrows + /// `self` will stay borrowed until the child Peripheral is dropped. + pub fn reborrow(&mut self) -> Peri<'_, T> { + // safety: we're returning the clone inside a new Peripheral that borrows // self, so user code can't use both at the same time. - PeripheralRef::new(unsafe { self.inner.clone_unchecked() }) + unsafe { self.clone_unchecked() } } /// Map the inner peripheral using `Into`. /// - /// This converts from `PeripheralRef<'a, T>` to `PeripheralRef<'a, U>`, using an + /// This converts from `Peri<'a, T>` to `Peri<'a, U>`, using an /// `Into` impl to convert from `T` to `U`. /// - /// For example, this can be useful to degrade GPIO pins: converting from PeripheralRef<'a, PB11>` to `PeripheralRef<'a, AnyPin>`. + /// For example, this can be useful to.into() GPIO pins: converting from Peri<'a, PB11>` to `Peri<'a, AnyPin>`. #[inline] - pub fn map_into(self) -> PeripheralRef<'a, U> + pub fn into(self) -> Peri<'a, U> where T: Into, + U: PeripheralType, { - PeripheralRef { - inner: self.inner.into(), - _lifetime: PhantomData, - } + unsafe { Peri::new_unchecked(self.inner.into()) } } } -impl<'a, T> Deref for PeripheralRef<'a, T> { +impl<'a, T: PeripheralType> Deref for Peri<'a, T> { type Target = T; #[inline] @@ -86,92 +86,5 @@ impl<'a, T> Deref for PeripheralRef<'a, T> { } } -/// 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`), -/// or borrowed peripherals (e.g. `&mut TWISPI0`). -/// -/// For example, if you have a driver with a constructor like this: -/// -/// ```ignore -/// impl<'d, T: Instance> Twim<'d, T> { -/// pub fn new( -/// twim: impl Peripheral

+ 'd, -/// irq: impl Peripheral

+ 'd, -/// sda: impl Peripheral

+ 'd, -/// scl: impl Peripheral

+ 'd, -/// config: Config, -/// ) -> Self { .. } -/// } -/// ``` -/// -/// You may call it with owned peripherals, which yields an instance that can live forever (`'static`): -/// -/// ```ignore -/// let mut twi: Twim<'static, ...> = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config); -/// ``` -/// -/// Or you may call it with borrowed peripherals, which yields an instance that can only live for as long -/// as the borrows last: -/// -/// ```ignore -/// let mut twi: Twim<'_, ...> = Twim::new(&mut p.TWISPI0, &mut irq, &mut p.P0_03, &mut p.P0_04, config); -/// ``` -/// -/// # Implementation details, for HAL authors -/// -/// When writing a HAL, the intended way to use this trait is to take `impl Peripheral

` in -/// the HAL's public API (such as driver constructors), calling `.into_ref()` to obtain a `PeripheralRef`, -/// and storing that in the driver struct. -/// -/// `.into_ref()` on an owned `T` yields a `PeripheralRef<'static, T>`. -/// `.into_ref()` on an `&'a mut T` yields a `PeripheralRef<'a, T>`. -pub trait Peripheral: Sized { - /// Peripheral singleton type - type P; - - /// Unsafely clone (duplicate) a peripheral singleton. - /// - /// # Safety - /// - /// This returns an owned clone of the peripheral. You must manually ensure - /// only one copy of the peripheral is in use at a time. For example, don't - /// create two SPI drivers on `SPI1`, because they will "fight" each other. - /// - /// You should strongly prefer using `into_ref()` instead. It returns a - /// `PeripheralRef`, which allows the borrow checker to enforce this at compile time. - unsafe fn clone_unchecked(&self) -> Self::P; - - /// Convert a value into a `PeripheralRef`. - /// - /// When called on an owned `T`, yields a `PeripheralRef<'static, T>`. - /// When called on an `&'a mut T`, yields a `PeripheralRef<'a, T>`. - #[inline] - fn into_ref<'a>(self) -> PeripheralRef<'a, Self::P> - where - Self: 'a, - { - PeripheralRef::new(unsafe { self.clone_unchecked() }) - } -} - -impl<'b, T: DerefMut> Peripheral for T -where - T::Target: Peripheral, -{ - type P = ::P; - - #[inline] - unsafe fn clone_unchecked(&self) -> Self::P { - T::Target::clone_unchecked(self) - } -} - -impl<'b, T: Peripheral> Peripheral for PeripheralRef<'_, T> { - type P = T::P; - - #[inline] - unsafe fn clone_unchecked(&self) -> Self::P { - T::clone_unchecked(self) - } -} +/// Marker trait for peripheral types. +pub trait PeripheralType: Copy + Sized {} diff --git a/embassy-imxrt/Cargo.toml b/embassy-imxrt/Cargo.toml new file mode 100644 index 000000000..49dc8089c --- /dev/null +++ b/embassy-imxrt/Cargo.toml @@ -0,0 +1,100 @@ +[package] +name = "embassy-imxrt" +version = "0.1.0" +edition = "2021" +license = "MIT" +description = "Embassy Hardware Abstraction Layer (HAL) for the IMXRT microcontroller" +keywords = ["embedded", "async", "imxrt", "rt600", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-imxrt" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-imxrt-v$VERSION/embassy-imxrt/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-imxrt/src/" +features = ["defmt", "unstable-pac", "time", "time-driver-os-timer"] +flavors = [ + { regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" } +] + +[package.metadata.docs.rs] +features = ["mimxrt685s", "defmt", "unstable-pac", "time", "time-driver"] +rustdoc-args = ["--cfg", "docsrs"] + +[features] +default = ["rt"] + +## Cortex-M runtime (enabled by default) +rt = [ + "mimxrt685s-pac?/rt", + "mimxrt633s-pac?/rt", +] + +## Enable defmt +defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mimxrt685s-pac?/defmt", "mimxrt633s-pac?/defmt"] + +## Enable features requiring `embassy-time` +time = ["dep:embassy-time", "embassy-embedded-hal/time"] + +## Enable custom embassy time-driver implementation, using 32KHz RTC +time-driver-rtc = ["_time-driver", "embassy-time-driver?/tick-hz-1_000"] + +## Enable custom embassy time-driver implementation, using 1MHz OS Timer +time-driver-os-timer = ["_time-driver", "embassy-time-driver?/tick-hz-1_000_000"] + +_time-driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] + +## Reexport the PAC for the currently enabled chip at `embassy_imxrt::pac` (unstable) +unstable-pac = [] + +# Features starting with `_` are for internal use only. They're not intended +# to be enabled by other crates, and are not covered by semver guarantees. + +_mimxrt685s = [] +_mimxrt633s = ["_espi"] + +# Peripherals +_espi = [] + +#! ### Chip selection features +## MIMXRT685S +mimxrt685s = ["mimxrt685s-pac", "_mimxrt685s"] +## MIMXRT633S +mimxrt633s = ["mimxrt633s-pac", "_mimxrt633s"] + +[dependencies] +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } +embassy-time = { version = "0.4", path = "../embassy-time", optional = true } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal", default-features = false } +embassy-futures = { version = "0.1.1", path = "../embassy-futures" } + +defmt = { version = "1.0.1", optional = true } +log = { version = "0.4.14", optional = true } +nb = "1.0.0" +cfg-if = "1.0.0" +cortex-m-rt = ">=0.7.3,<0.8" +cortex-m = "0.7.6" +critical-section = "1.1" +embedded-io = { version = "0.6.1" } +embedded-io-async = { version = "0.6.1" } +fixed = "1.23.1" + +rand-core-06 = { package = "rand_core", version = "0.6" } +rand-core-09 = { package = "rand_core", version = "0.9" } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ + "unproven", +] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } + +document-features = "0.2.7" +paste = "1.0" + +# PACs +mimxrt685s-pac = { version = "0.4.0", optional = true, features = ["rt", "critical-section"] } +mimxrt633s-pac = { version = "0.4.0", optional = true, features = ["rt", "critical-section"] } diff --git a/embassy-imxrt/README.md b/embassy-imxrt/README.md new file mode 100644 index 000000000..cfdfb8ce2 --- /dev/null +++ b/embassy-imxrt/README.md @@ -0,0 +1,59 @@ +# Embassy iMXRT HAL + +## Introduction + +HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so +raw register manipulation is not needed. + +The Embassy iMXRT HAL targets the NXP iMXRT Family of MCUs. The HAL implements +both blocking and async APIs for many peripherals. The benefit of using the +async APIs is that the HAL takes care of waiting for peripherals to complete +operations in low power mode and handling of interrupts, so that applications +can focus on business logic. + +NOTE: The Embassy HALs can be used both for non-async and async operations. For +async, you can choose which runtime you want to use. + +For a complete list of available peripherals and features, see the +[embassy-imxrt documentation](https://docs.embassy.dev/embassy-imxrt). + +## Hardware support + +The `embassy-imxrt` HAL currently supports two main variants of the iMXRT +family: + +* MIMXRT685S + ([examples](https://github.com/OpenDevicePartnership/embassy-imxrt/tree/main/examples/rt685s-evk)) +* MIMXRT633s + ([examples](https://github.com/OpenDevicePartnership/embassy-imxrt/tree/main/examples/rt633)) + +Several peripherals are supported and tested on both supported chip variants. To +check what's available, make sure to the MCU you're targetting in the top menu +in the [documentation](https://docs.embassy.dev/embassy-imxrt). + +## TrustZone support + +TrustZone support is yet to be implemented. + +## Time driver + +If the `time-driver` feature is enabled, the HAL uses the RTC peripheral as a +global time driver for [embassy-time](https://crates.io/crates/embassy-time), +with a tick rate of 32768 Hz. + +## Embedded-hal + +The `embassy-imxrt` HAL implements the traits from +[embedded-hal](https://crates.io/crates/embedded-hal) (v0.2 and 1.0) and +[embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as +[embedded-io](https://crates.io/crates/embedded-io) and +[embedded-io-async](https://crates.io/crates/embedded-io-async). + +## Interoperability + +This crate can run on any executor. + +Optionally, some features requiring +[`embassy-time`](https://crates.io/crates/embassy-time) can be activated with +the `time` feature. If you enable it, you must link an `embassy-time` driver in +your project. diff --git a/embassy-imxrt/src/chips/mimxrt633s.rs b/embassy-imxrt/src/chips/mimxrt633s.rs new file mode 100644 index 000000000..30072132a --- /dev/null +++ b/embassy-imxrt/src/chips/mimxrt633s.rs @@ -0,0 +1,389 @@ +pub use mimxrt633s_pac as pac; + +#[allow(clippy::missing_safety_doc)] +pub mod interrupts { + embassy_hal_internal::interrupt_mod!( + ACMP, + ADC0, + CASPER, + CTIMER0, + CTIMER1, + CTIMER2, + CTIMER3, + CTIMER4, + DMA0, + DMA1, + DMIC0, + ESPI, + FLEXCOMM0, + FLEXCOMM1, + FLEXCOMM14, + FLEXCOMM15, + FLEXCOMM2, + FLEXCOMM3, + FLEXCOMM4, + FLEXCOMM5, + FLEXCOMM6, + FLEXCOMM7, + FLEXSPI, + GPIO_INTA, + GPIO_INTB, + HASHCRYPT, + HWVAD0, + HYPERVISOR, + I3C0, + MRT0, + MU_A, + OS_EVENT, + PIN_INT0, + PIN_INT1, + PIN_INT2, + PIN_INT3, + PIN_INT4, + PIN_INT5, + PIN_INT6, + PIN_INT7, + PMC_PMIC, + POWERQUAD, + PUF, + RNG, + RTC, + SCT0, + SECUREVIOLATION, + SGPIO_INTA, + SGPIO_INTB, + USB, + USBPHY_DCD, + USB_WAKEUP, + USDHC0, + USDHC1, + UTICK0, + WDT0, + WDT1, + ); +} + +embassy_hal_internal::peripherals!( + ACMP, + ADC0, + CASPER, + CRC, + CTIMER0_COUNT_CHANNEL0, + CTIMER0_COUNT_CHANNEL1, + CTIMER0_COUNT_CHANNEL2, + CTIMER0_COUNT_CHANNEL3, + CTIMER0_CAPTURE_CHANNEL0, + CTIMER0_CAPTURE_CHANNEL1, + CTIMER0_CAPTURE_CHANNEL2, + CTIMER0_CAPTURE_CHANNEL3, + CTIMER1_COUNT_CHANNEL0, + CTIMER1_COUNT_CHANNEL1, + CTIMER1_COUNT_CHANNEL2, + CTIMER1_COUNT_CHANNEL3, + CTIMER1_CAPTURE_CHANNEL0, + CTIMER1_CAPTURE_CHANNEL1, + CTIMER1_CAPTURE_CHANNEL2, + CTIMER1_CAPTURE_CHANNEL3, + CTIMER2_COUNT_CHANNEL0, + CTIMER2_COUNT_CHANNEL1, + CTIMER2_COUNT_CHANNEL2, + CTIMER2_COUNT_CHANNEL3, + CTIMER2_CAPTURE_CHANNEL0, + CTIMER2_CAPTURE_CHANNEL1, + CTIMER2_CAPTURE_CHANNEL2, + CTIMER2_CAPTURE_CHANNEL3, + CTIMER3_COUNT_CHANNEL0, + CTIMER3_COUNT_CHANNEL1, + CTIMER3_COUNT_CHANNEL2, + CTIMER3_COUNT_CHANNEL3, + CTIMER3_CAPTURE_CHANNEL0, + CTIMER3_CAPTURE_CHANNEL1, + CTIMER3_CAPTURE_CHANNEL2, + CTIMER3_CAPTURE_CHANNEL3, + CTIMER4_COUNT_CHANNEL0, + CTIMER4_COUNT_CHANNEL1, + CTIMER4_COUNT_CHANNEL2, + CTIMER4_COUNT_CHANNEL3, + CTIMER4_CAPTURE_CHANNEL0, + CTIMER4_CAPTURE_CHANNEL1, + CTIMER4_CAPTURE_CHANNEL2, + CTIMER4_CAPTURE_CHANNEL3, + DMA0, + DMA0_CH0, + DMA0_CH1, + DMA0_CH2, + DMA0_CH3, + DMA0_CH4, + DMA0_CH5, + DMA0_CH6, + DMA0_CH7, + DMA0_CH8, + DMA0_CH9, + DMA0_CH10, + DMA0_CH11, + DMA0_CH12, + DMA0_CH13, + DMA0_CH14, + DMA0_CH15, + DMA0_CH16, + DMA0_CH17, + DMA0_CH18, + DMA0_CH19, + DMA0_CH20, + DMA0_CH21, + DMA0_CH22, + DMA0_CH23, + DMA0_CH24, + DMA0_CH25, + DMA0_CH26, + DMA0_CH27, + DMA0_CH28, + DMA0_CH29, + DMA0_CH30, + DMA0_CH31, + DMA0_CH32, + DMA1, + DMA1_CH0, + DMA1_CH1, + DMA1_CH2, + DMA1_CH3, + DMA1_CH4, + DMA1_CH5, + DMA1_CH6, + DMA1_CH7, + DMA1_CH8, + DMA1_CH9, + DMA1_CH10, + DMA1_CH11, + DMA1_CH12, + DMA1_CH13, + DMA1_CH14, + DMA1_CH15, + DMA1_CH16, + DMA1_CH17, + DMA1_CH18, + DMA1_CH19, + DMA1_CH20, + DMA1_CH21, + DMA1_CH22, + DMA1_CH23, + DMA1_CH24, + DMA1_CH25, + DMA1_CH26, + DMA1_CH27, + DMA1_CH28, + DMA1_CH29, + DMA1_CH30, + DMA1_CH31, + DMA1_CH32, + DMIC0, + DSPWAKE, + ESPI, + FLEXCOMM0, + FLEXCOMM1, + FLEXCOMM14, + FLEXCOMM15, + FLEXCOMM2, + FLEXCOMM3, + FLEXCOMM4, + FLEXCOMM5, + FLEXCOMM6, + FLEXCOMM7, + FLEXSPI, + FREQME, + GPIO_INTA, + GPIO_INTB, + HASHCRYPT, + HSGPIO0, + HSGPIO1, + HSGPIO2, + HSGPIO3, + HSGPIO4, + HSGPIO5, + HSGPIO6, + HSGPIO7, + HWVAD0, + HYPERVISOR, + I3C0, + MRT0, + MU_A, + OS_EVENT, + PIN_INT0, + PIN_INT1, + PIN_INT2, + PIN_INT3, + PIN_INT4, + PIN_INT5, + PIN_INT6, + PIN_INT7, + PIO0_0, + PIO0_1, + PIO0_10, + PIO0_11, + PIO0_12, + PIO0_13, + PIO0_14, + PIO0_15, + PIO0_16, + PIO0_17, + PIO0_18, + PIO0_19, + PIO0_2, + PIO0_20, + PIO0_21, + PIO0_22, + PIO0_23, + PIO0_24, + PIO0_25, + PIO0_26, + PIO0_27, + PIO0_28, + PIO0_29, + PIO0_3, + PIO0_30, + PIO0_31, + PIO0_4, + PIO0_5, + PIO0_6, + PIO0_7, + PIO0_8, + PIO0_9, + PIO1_0, + PIO1_1, + PIO1_10, + PIO1_11, + PIO1_12, + PIO1_13, + PIO1_14, + PIO1_15, + PIO1_16, + PIO1_17, + PIO1_18, + PIO1_19, + PIO1_2, + PIO1_20, + PIO1_21, + PIO1_22, + PIO1_23, + PIO1_24, + PIO1_25, + PIO1_26, + PIO1_27, + PIO1_28, + PIO1_29, + PIO1_3, + PIO1_30, + PIO1_31, + PIO1_4, + PIO1_5, + PIO1_6, + PIO1_7, + PIO1_8, + PIO1_9, + PIO2_0, + PIO2_1, + PIO2_10, + PIO2_11, + PIO2_12, + PIO2_13, + PIO2_14, + PIO2_15, + PIO2_16, + PIO2_17, + PIO2_18, + PIO2_19, + PIO2_2, + PIO2_20, + PIO2_21, + PIO2_22, + PIO2_23, + PIO2_24, + PIO2_25, + PIO2_26, + PIO2_27, + PIO2_28, + PIO2_29, + PIO2_3, + PIO2_30, + PIO2_31, + PIO2_4, + PIO2_5, + PIO2_6, + PIO2_7, + PIO2_8, + PIO2_9, + PIO3_0, + PIO3_1, + PIO3_10, + PIO3_11, + PIO3_12, + PIO3_13, + PIO3_14, + PIO3_15, + PIO3_16, + PIO3_17, + PIO3_18, + PIO3_19, + PIO3_2, + PIO3_20, + PIO3_21, + PIO3_22, + PIO3_23, + PIO3_24, + PIO3_25, + PIO3_26, + PIO3_27, + PIO3_28, + PIO3_29, + PIO3_3, + PIO3_30, + PIO3_31, + PIO3_4, + PIO3_5, + PIO3_6, + PIO3_7, + PIO3_8, + PIO3_9, + PIO4_0, + PIO4_1, + PIO4_10, + PIO4_2, + PIO4_3, + PIO4_4, + PIO4_5, + PIO4_6, + PIO4_7, + PIO4_8, + PIO4_9, + PIO7_24, + PIO7_25, + PIO7_26, + PIO7_27, + PIO7_28, + PIO7_29, + PIO7_30, + PIO7_31, + PIOFC15_SCL, + PIOFC15_SDA, + PMC_PMIC, + PIMCTL, + POWERQUAD, + PUF, + RNG, + RTC, + SCT0, + SECGPIO, + SECUREVIOLATION, + SEMA42, + SGPIO_INTA, + SGPIO_INTB, + USBHSD, + USBHSH, + USBPHY, + USB_WAKEUP, + USDHC0, + USDHC1, + UTICK0, + WDT0, + WDT1, +); diff --git a/embassy-imxrt/src/chips/mimxrt685s.rs b/embassy-imxrt/src/chips/mimxrt685s.rs new file mode 100644 index 000000000..746861e35 --- /dev/null +++ b/embassy-imxrt/src/chips/mimxrt685s.rs @@ -0,0 +1,388 @@ +pub use mimxrt685s_pac as pac; + +#[allow(clippy::missing_safety_doc)] +pub mod interrupts { + embassy_hal_internal::interrupt_mod!( + ACMP, + ADC0, + CASPER, + CTIMER0, + CTIMER1, + CTIMER2, + CTIMER3, + CTIMER4, + DMA0, + DMA1, + DMIC0, + DSPWAKE, + FLEXCOMM0, + FLEXCOMM1, + FLEXCOMM14, + FLEXCOMM15, + FLEXCOMM2, + FLEXCOMM3, + FLEXCOMM4, + FLEXCOMM5, + FLEXCOMM6, + FLEXCOMM7, + FLEXSPI, + GPIO_INTA, + GPIO_INTB, + HASHCRYPT, + HWVAD0, + HYPERVISOR, + I3C0, + MRT0, + MU_A, + OS_EVENT, + PIN_INT0, + PIN_INT1, + PIN_INT2, + PIN_INT3, + PIN_INT4, + PIN_INT5, + PIN_INT6, + PIN_INT7, + PMC_PMIC, + POWERQUAD, + PUF, + RNG, + RTC, + SCT0, + SECUREVIOLATION, + SGPIO_INTA, + SGPIO_INTB, + USB, + USBPHY_DCD, + USB_WAKEUP, + USDHC0, + USDHC1, + UTICK0, + WDT0, + WDT1, + ); +} + +embassy_hal_internal::peripherals!( + ACMP, + ADC0, + CASPER, + CRC, + CTIMER0_COUNT_CHANNEL0, + CTIMER0_COUNT_CHANNEL1, + CTIMER0_COUNT_CHANNEL2, + CTIMER0_COUNT_CHANNEL3, + CTIMER0_CAPTURE_CHANNEL0, + CTIMER0_CAPTURE_CHANNEL1, + CTIMER0_CAPTURE_CHANNEL2, + CTIMER0_CAPTURE_CHANNEL3, + CTIMER1_COUNT_CHANNEL0, + CTIMER1_COUNT_CHANNEL1, + CTIMER1_COUNT_CHANNEL2, + CTIMER1_COUNT_CHANNEL3, + CTIMER1_CAPTURE_CHANNEL0, + CTIMER1_CAPTURE_CHANNEL1, + CTIMER1_CAPTURE_CHANNEL2, + CTIMER1_CAPTURE_CHANNEL3, + CTIMER2_COUNT_CHANNEL0, + CTIMER2_COUNT_CHANNEL1, + CTIMER2_COUNT_CHANNEL2, + CTIMER2_COUNT_CHANNEL3, + CTIMER2_CAPTURE_CHANNEL0, + CTIMER2_CAPTURE_CHANNEL1, + CTIMER2_CAPTURE_CHANNEL2, + CTIMER2_CAPTURE_CHANNEL3, + CTIMER3_COUNT_CHANNEL0, + CTIMER3_COUNT_CHANNEL1, + CTIMER3_COUNT_CHANNEL2, + CTIMER3_COUNT_CHANNEL3, + CTIMER3_CAPTURE_CHANNEL0, + CTIMER3_CAPTURE_CHANNEL1, + CTIMER3_CAPTURE_CHANNEL2, + CTIMER3_CAPTURE_CHANNEL3, + CTIMER4_COUNT_CHANNEL0, + CTIMER4_COUNT_CHANNEL1, + CTIMER4_COUNT_CHANNEL2, + CTIMER4_COUNT_CHANNEL3, + CTIMER4_CAPTURE_CHANNEL0, + CTIMER4_CAPTURE_CHANNEL1, + CTIMER4_CAPTURE_CHANNEL2, + CTIMER4_CAPTURE_CHANNEL3, + DMA0, + DMA0_CH0, + DMA0_CH1, + DMA0_CH2, + DMA0_CH3, + DMA0_CH4, + DMA0_CH5, + DMA0_CH6, + DMA0_CH7, + DMA0_CH8, + DMA0_CH9, + DMA0_CH10, + DMA0_CH11, + DMA0_CH12, + DMA0_CH13, + DMA0_CH14, + DMA0_CH15, + DMA0_CH16, + DMA0_CH17, + DMA0_CH18, + DMA0_CH19, + DMA0_CH20, + DMA0_CH21, + DMA0_CH22, + DMA0_CH23, + DMA0_CH24, + DMA0_CH25, + DMA0_CH26, + DMA0_CH27, + DMA0_CH28, + DMA0_CH29, + DMA0_CH30, + DMA0_CH31, + DMA0_CH32, + DMA1, + DMA1_CH0, + DMA1_CH1, + DMA1_CH2, + DMA1_CH3, + DMA1_CH4, + DMA1_CH5, + DMA1_CH6, + DMA1_CH7, + DMA1_CH8, + DMA1_CH9, + DMA1_CH10, + DMA1_CH11, + DMA1_CH12, + DMA1_CH13, + DMA1_CH14, + DMA1_CH15, + DMA1_CH16, + DMA1_CH17, + DMA1_CH18, + DMA1_CH19, + DMA1_CH20, + DMA1_CH21, + DMA1_CH22, + DMA1_CH23, + DMA1_CH24, + DMA1_CH25, + DMA1_CH26, + DMA1_CH27, + DMA1_CH28, + DMA1_CH29, + DMA1_CH30, + DMA1_CH31, + DMA1_CH32, + DMIC0, + DSPWAKE, + FLEXCOMM0, + FLEXCOMM1, + FLEXCOMM14, + FLEXCOMM15, + FLEXCOMM2, + FLEXCOMM3, + FLEXCOMM4, + FLEXCOMM5, + FLEXCOMM6, + FLEXCOMM7, + FLEXSPI, + FREQME, + GPIO_INTA, + GPIO_INTB, + HASHCRYPT, + HSGPIO0, + HSGPIO1, + HSGPIO2, + HSGPIO3, + HSGPIO4, + HSGPIO5, + HSGPIO6, + HSGPIO7, + HWVAD0, + HYPERVISOR, + I3C0, + MRT0, + MU_A, + OS_EVENT, + PIN_INT0, + PIN_INT1, + PIN_INT2, + PIN_INT3, + PIN_INT4, + PIN_INT5, + PIN_INT6, + PIN_INT7, + PIO0_0, + PIO0_1, + PIO0_10, + PIO0_11, + PIO0_12, + PIO0_13, + PIO0_14, + PIO0_15, + PIO0_16, + PIO0_17, + PIO0_18, + PIO0_19, + PIO0_2, + PIO0_20, + PIO0_21, + PIO0_22, + PIO0_23, + PIO0_24, + PIO0_25, + PIO0_26, + PIO0_27, + PIO0_28, + PIO0_29, + PIO0_3, + PIO0_30, + PIO0_31, + PIO0_4, + PIO0_5, + PIO0_6, + PIO0_7, + PIO0_8, + PIO0_9, + PIO1_0, + PIO1_1, + PIO1_10, + PIO1_11, + PIO1_12, + PIO1_13, + PIO1_14, + PIO1_15, + PIO1_16, + PIO1_17, + PIO1_18, + PIO1_19, + PIO1_2, + PIO1_20, + PIO1_21, + PIO1_22, + PIO1_23, + PIO1_24, + PIO1_25, + PIO1_26, + PIO1_27, + PIO1_28, + PIO1_29, + PIO1_3, + PIO1_30, + PIO1_31, + PIO1_4, + PIO1_5, + PIO1_6, + PIO1_7, + PIO1_8, + PIO1_9, + PIO2_0, + PIO2_1, + PIO2_10, + PIO2_11, + PIO2_12, + PIO2_13, + PIO2_14, + PIO2_15, + PIO2_16, + PIO2_17, + PIO2_18, + PIO2_19, + PIO2_2, + PIO2_20, + PIO2_21, + PIO2_22, + PIO2_23, + PIO2_24, + PIO2_25, + PIO2_26, + PIO2_27, + PIO2_28, + PIO2_29, + PIO2_3, + PIO2_30, + PIO2_31, + PIO2_4, + PIO2_5, + PIO2_6, + PIO2_7, + PIO2_8, + PIO2_9, + PIO3_0, + PIO3_1, + PIO3_10, + PIO3_11, + PIO3_12, + PIO3_13, + PIO3_14, + PIO3_15, + PIO3_16, + PIO3_17, + PIO3_18, + PIO3_19, + PIO3_2, + PIO3_20, + PIO3_21, + PIO3_22, + PIO3_23, + PIO3_24, + PIO3_25, + PIO3_26, + PIO3_27, + PIO3_28, + PIO3_29, + PIO3_3, + PIO3_30, + PIO3_31, + PIO3_4, + PIO3_5, + PIO3_6, + PIO3_7, + PIO3_8, + PIO3_9, + PIO4_0, + PIO4_1, + PIO4_10, + PIO4_2, + PIO4_3, + PIO4_4, + PIO4_5, + PIO4_6, + PIO4_7, + PIO4_8, + PIO4_9, + PIO7_24, + PIO7_25, + PIO7_26, + PIO7_27, + PIO7_28, + PIO7_29, + PIO7_30, + PIO7_31, + PIOFC15_SCL, + PIOFC15_SDA, + PMC_PMIC, + PIMCTL, + POWERQUAD, + PUF, + RNG, + RTC, + SCT0, + SECGPIO, + SECUREVIOLATION, + SEMA42, + SGPIO_INTA, + SGPIO_INTB, + USBHSD, + USBHSH, + USBPHY, + USB_WAKEUP, + USDHC0, + USDHC1, + UTICK0, + WDT0, + WDT1, +); diff --git a/embassy-imxrt/src/clocks.rs b/embassy-imxrt/src/clocks.rs new file mode 100644 index 000000000..39c3e6238 --- /dev/null +++ b/embassy-imxrt/src/clocks.rs @@ -0,0 +1,1661 @@ +//! Clock configuration for the `RT6xx` +use core::sync::atomic::{AtomicU32, AtomicU8, Ordering}; + +use paste::paste; + +use crate::pac; + +/// Clock configuration; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Clocks { + /// Low power oscillator + Lposc, + /// System Frequency Resonance Oscillator (SFRO) + Sfro, + /// Real Time Clock + Rtc, + /// Feed-forward Ring Oscillator + Ffro, // This includes that div2 and div4 variations + /// External Clock Input + ClkIn, + /// AHB Clock + Hclk, + /// Main Clock + MainClk, + /// Main PLL Clock + MainPllClk, // also has aux0,aux1,dsp, and audio pll's downstream + /// System Clock + SysClk, + /// System Oscillator + SysOscClk, + /// ADC Clock + Adc, +} + +/// Clock configuration. +pub struct ClockConfig { + /// low-power oscillator config + pub lposc: LposcConfig, + /// 16Mhz internal oscillator config + pub sfro: SfroConfig, + /// Real Time Clock config + pub rtc: RtcClkConfig, + /// 48/60 Mhz internal oscillator config + pub ffro: FfroConfig, + // pub pll: Option, //potentially covered in main pll clk + /// External Clock-In config + pub clk_in: ClkInConfig, + /// AHB bus clock config + pub hclk: HclkConfig, + /// Main Clock config + pub main_clk: MainClkConfig, + /// Main Pll clock config + pub main_pll_clk: MainPllClkConfig, + /// Software concept to be used with systick, doesn't map to a register + pub sys_clk: SysClkConfig, + /// System Oscillator Config + pub sys_osc: SysOscConfig, + // todo: move ADC here +} + +impl ClockConfig { + /// Clock configuration derived from external crystal. + #[must_use] + pub fn crystal() -> Self { + const CORE_CPU_FREQ: u32 = 500_000_000; + const PLL_CLK_FREQ: u32 = 528_000_000; + const SYS_CLK_FREQ: u32 = CORE_CPU_FREQ / 2; + Self { + lposc: LposcConfig { + state: State::Enabled, + freq: AtomicU32::new(Into::into(LposcFreq::Lp1m)), + }, + sfro: SfroConfig { state: State::Enabled }, + rtc: RtcClkConfig { + state: State::Enabled, + wake_alarm_state: State::Disabled, + sub_second_state: State::Disabled, + freq: AtomicU32::new(Into::into(RtcFreq::Default1Hz)), + rtc_int: RtcInterrupts::None, + }, + ffro: FfroConfig { + state: State::Enabled, + freq: AtomicU32::new(Into::into(FfroFreq::Ffro48m)), + }, + //pll: Some(PllConfig {}),//includes aux0 and aux1 pll + clk_in: ClkInConfig { + state: State::Disabled, + // This is an externally sourced clock + // Don't give it an initial frequency + freq: Some(AtomicU32::new(0)), + }, + hclk: HclkConfig { state: State::Disabled }, + main_clk: MainClkConfig { + state: State::Enabled, + //FFRO divided by 4 is reset values of Main Clk Sel A, Sel B + src: MainClkSrc::FFRO, + div_int: AtomicU32::new(4), + freq: AtomicU32::new(CORE_CPU_FREQ), + }, + main_pll_clk: MainPllClkConfig { + state: State::Enabled, + src: MainPllClkSrc::SFRO, + freq: AtomicU32::new(PLL_CLK_FREQ), + mult: AtomicU8::new(16), + pfd0: 19, // + pfd1: 0, // future field + pfd2: 19, // 0x13 + pfd3: 0, // future field + aux0_div: 0, + aux1_div: 0, + }, + sys_clk: SysClkConfig { + sysclkfreq: AtomicU32::new(SYS_CLK_FREQ), + }, + sys_osc: SysOscConfig { state: State::Enabled }, + //adc: Some(AdcConfig {}), // TODO: add config + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Clock state enum +pub enum State { + /// Clock is enabled + Enabled, + /// Clock is disabled + Disabled, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Low Power Oscillator valid frequencies +pub enum LposcFreq { + /// 1 `MHz` oscillator + Lp1m, + /// 32kHz oscillator + Lp32k, +} + +impl From for u32 { + fn from(value: LposcFreq) -> Self { + match value { + LposcFreq::Lp1m => 1_000_000, + LposcFreq::Lp32k => 32_768, + } + } +} + +impl TryFrom for LposcFreq { + type Error = ClockError; + fn try_from(value: u32) -> Result { + match value { + 1_000_000 => Ok(LposcFreq::Lp1m), + 32_768 => Ok(LposcFreq::Lp32k), + _ => Err(ClockError::InvalidFrequency), + } + } +} + +/// Low power oscillator config +pub struct LposcConfig { + state: State, + // low power osc + freq: AtomicU32, +} + +const SFRO_FREQ: u32 = 16_000_000; +/// SFRO config +pub struct SfroConfig { + state: State, +} + +/// Valid RTC frequencies +pub enum RtcFreq { + /// "Alarm" aka 1Hz clock + Default1Hz, + /// "Wake" aka 1kHz clock + HighResolution1khz, + /// 32kHz clock + SubSecond32kHz, +} + +impl From for u32 { + fn from(value: RtcFreq) -> Self { + match value { + RtcFreq::Default1Hz => 1, + RtcFreq::HighResolution1khz => 1_000, + RtcFreq::SubSecond32kHz => 32_768, + } + } +} + +impl TryFrom for RtcFreq { + type Error = ClockError; + fn try_from(value: u32) -> Result { + match value { + 1 => Ok(RtcFreq::Default1Hz), + 1_000 => Ok(RtcFreq::HighResolution1khz), + 32_768 => Ok(RtcFreq::SubSecond32kHz), + _ => Err(ClockError::InvalidFrequency), + } + } +} + +/// RTC Interrupt options +pub enum RtcInterrupts { + /// No interrupts are set + None, + /// 1Hz RTC clock aka Alarm interrupt set + Alarm, + /// 1kHz RTC clock aka Wake interrupt set + Wake, +} + +impl From for u8 { + fn from(value: RtcInterrupts) -> Self { + match value { + RtcInterrupts::None => 0b00, + RtcInterrupts::Alarm => 0b01, + RtcInterrupts::Wake => 0b10, + } + } +} +/// RTC clock config. +pub struct RtcClkConfig { + /// 1 Hz Clock state + pub state: State, + /// 1kHz Clock state + pub wake_alarm_state: State, + /// 32kHz Clock state + pub sub_second_state: State, + /// RTC clock source. + pub freq: AtomicU32, + /// RTC Interrupt + pub rtc_int: RtcInterrupts, +} + +/// Valid FFRO Frequencies +pub enum FfroFreq { + /// 48 Mhz Internal Oscillator + Ffro48m, + /// 60 `MHz` Internal Oscillator + Ffro60m, +} + +/// FFRO Clock Config +pub struct FfroConfig { + /// FFRO Clock state + state: State, + /// FFRO Frequency + freq: AtomicU32, +} + +impl From for u32 { + fn from(value: FfroFreq) -> Self { + match value { + FfroFreq::Ffro48m => 48_000_000, + FfroFreq::Ffro60m => 60_000_000, + } + } +} + +impl TryFrom for FfroFreq { + type Error = ClockError; + fn try_from(value: u32) -> Result { + match value { + 48_000_000 => Ok(FfroFreq::Ffro48m), + 60_000_000 => Ok(FfroFreq::Ffro60m), + _ => Err(ClockError::InvalidFrequency), + } + } +} + +/// PLL clock source +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MainPllClkSrc { + /// SFRO + SFRO, + /// External Clock + ClkIn, + /// FFRO + FFRO, +} + +/// Transform from Source Clock enum to Clocks +impl From for Clocks { + fn from(value: MainPllClkSrc) -> Self { + match value { + MainPllClkSrc::SFRO => Clocks::Sfro, + MainPllClkSrc::ClkIn => Clocks::ClkIn, + MainPllClkSrc::FFRO => Clocks::Ffro, + } + } +} + +impl TryFrom for MainPllClkSrc { + type Error = ClockError; + fn try_from(value: Clocks) -> Result { + match value { + Clocks::Sfro => Ok(MainPllClkSrc::SFRO), + Clocks::Ffro => Ok(MainPllClkSrc::FFRO), + Clocks::ClkIn => Ok(MainPllClkSrc::ClkIn), + _ => Err(ClockError::ClockNotSupported), + } + } +} + +/// PLL configuration. +pub struct MainPllClkConfig { + /// Clock active state + pub state: State, + /// Main clock source. + pub src: MainPllClkSrc, + /// Main clock frequency + pub freq: AtomicU32, + //TODO: numerator and denominator not used but present in register + /// Multiplication factor. + pub mult: AtomicU8, + // the following are actually 6-bits not 8 + /// Fractional divider 0, main pll clock + pub pfd0: u8, + /// Fractional divider 1 + pub pfd1: u8, + /// Fractional divider 2 + pub pfd2: u8, + /// Fractional divider 3 + pub pfd3: u8, + // Aux dividers + /// aux divider 0 + pub aux0_div: u8, + /// aux divider 1 + pub aux1_div: u8, +} +/// External input clock config +pub struct ClkInConfig { + /// External clock input state + state: State, + /// External clock input rate + freq: Option, +} + +/// AHB clock config +pub struct HclkConfig { + /// divider to turn main clk into hclk for AHB bus + pub state: State, +} + +/// Main clock source. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MainClkSrc { + /// FFRO divided by 4 + FFROdiv4, // probably don't need since it'll be covered by div_int + /// External Clock + ClkIn, + /// Low Power Oscillator + Lposc, + /// FFRO + FFRO, + /// SFRO + SFRO, + /// Main PLL Clock + PllMain, + /// RTC 32kHz oscillator. + RTC32k, +} + +impl From for Clocks { + fn from(value: MainClkSrc) -> Self { + match value { + MainClkSrc::ClkIn => Clocks::ClkIn, + MainClkSrc::Lposc => Clocks::Lposc, + MainClkSrc::FFRO => Clocks::Ffro, + MainClkSrc::SFRO => Clocks::Sfro, + MainClkSrc::PllMain => Clocks::MainPllClk, + MainClkSrc::RTC32k => Clocks::Rtc, + MainClkSrc::FFROdiv4 => Clocks::Ffro, + } + } +} + +impl TryFrom for MainClkSrc { + type Error = ClockError; + fn try_from(value: Clocks) -> Result { + match value { + Clocks::ClkIn => Ok(MainClkSrc::ClkIn), + Clocks::Lposc => Ok(MainClkSrc::Lposc), + Clocks::Sfro => Ok(MainClkSrc::SFRO), + Clocks::MainPllClk => Ok(MainClkSrc::PllMain), + Clocks::Rtc => Ok(MainClkSrc::RTC32k), + Clocks::Ffro => Ok(MainClkSrc::FFRO), + _ => Err(ClockError::ClockNotSupported), + } + } +} + +/// Main clock config. +pub struct MainClkConfig { + /// Main clock state + pub state: State, + /// Main clock source. + pub src: MainClkSrc, + /// Main clock divider. + pub div_int: AtomicU32, + /// Clock Frequency + pub freq: AtomicU32, +} + +/// System Core Clock config, SW concept for systick +pub struct SysClkConfig { + /// keeps track of the system core clock frequency + /// future use with systick + pub sysclkfreq: AtomicU32, +} + +/// System Oscillator Config +pub struct SysOscConfig { + /// Clock State + pub state: State, +} +const SYS_OSC_DEFAULT_FREQ: u32 = 24_000_000; + +/// Clock Errors +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockError { + /// Error due to attempting to change a clock with the wrong config block + ClockMismatch, + /// Error due to attempting to modify a clock that's not yet been enabled + ClockNotEnabled, + /// Error due to attempting to set a clock source that's not a supported option + ClockNotSupported, + /// Error due to attempting to set a clock to an invalid frequency + InvalidFrequency, + /// Error due to attempting to modify a clock output with an invalid divider + InvalidDiv, + /// Error due to attempting to modify a clock output with an invalid multiplier + InvalidMult, +} + +/// Trait to configure one of the clocks +pub trait ConfigurableClock { + /// Reset the clock, will enable it + fn disable(&self) -> Result<(), ClockError>; + /// Enable the clock + fn enable_and_reset(&self) -> Result<(), ClockError>; + /// Return the clock rate (Hz) + fn get_clock_rate(&self) -> Result; + /// Set the desired clock rate (Hz) + fn set_clock_rate(&mut self, div: u8, mult: u8, freq: u32) -> Result<(), ClockError>; + /// Returns whether this clock is enabled + fn is_enabled(&self) -> bool; +} + +impl LposcConfig { + /// Initializes low-power oscillator. + fn init_lposc() { + // Enable low power oscillator + // SAFETY: unsafe needed to take pointer to Sysctl0, only happens once during init + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + sysctl0.pdruncfg0_clr().write(|w| w.lposc_pd().clr_pdruncfg0()); + + // Wait for low-power oscillator to be ready (typically 64 us) + // Busy loop seems better here than trying to shoe-in an async delay + // SAFETY: unsafe needed to take pointer to Clkctl0, needed to validate HW is ready + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + while clkctl0.lposcctl0().read().clkrdy().bit_is_clear() {} + } +} +impl ConfigurableClock for LposcConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + LposcConfig::init_lposc(); + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + // SAFETY: unsafe needed to take pointer to Sysctl0, needed to power down the LPOSC HW + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + sysctl0.pdruncfg0_set().write(|w| w.lposc_pd().set_pdruncfg0()); + // Wait until LPOSC disabled + while !sysctl0.pdruncfg0().read().lposc_pd().is_power_down() {} + Ok(()) + } + fn get_clock_rate(&self) -> Result { + Ok(self.freq.load(Ordering::Relaxed)) + } + fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { + if let Ok(r) = >::try_into(freq) { + match r { + LposcFreq::Lp1m => { + self.freq + .store(LposcFreq::Lp1m as u32, core::sync::atomic::Ordering::Relaxed); + Ok(()) + } + LposcFreq::Lp32k => { + self.freq + .store(LposcFreq::Lp1m as u32, core::sync::atomic::Ordering::Relaxed); + Ok(()) + } + } + } else { + Err(ClockError::InvalidFrequency) + } + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } +} + +impl FfroConfig { + /// Necessary register writes to initialize the FFRO clock + pub fn init_ffro_clk() { + // SAFETY: unsafe needed to take pointer to Sysctl0, only to power up FFRO + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + + /* Power on FFRO (48/60MHz) */ + sysctl0.pdruncfg0_clr().write(|w| w.ffro_pd().clr_pdruncfg0()); + + // SAFETY: unsafe needed to take pointer to Clkctl0, only to set proper ffro update mode + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + + clkctl0.ffroctl1().write(|w| w.update().normal_mode()); + + // No FFRO enable/disable control in CLKCTL. + // Delay enough for FFRO to be stable in case it was just powered on + delay_loop_clocks(50, 12_000_000); + } +} + +impl ConfigurableClock for FfroConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + // SAFETY: should be called once + FfroConfig::init_ffro_clk(); + // default is 48 MHz + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + // SAFETY: unsafe needed to take pointer to Sysctl0, only to power down FFRO + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + sysctl0.pdruncfg0_set().write(|w| w.ffro_pd().set_pdruncfg0()); + delay_loop_clocks(30, 12_000_000); + // Wait until FFRO disabled + while !sysctl0.pdruncfg0().read().ffro_pd().is_power_down() {} + Ok(()) + } + fn get_clock_rate(&self) -> Result { + Ok(self.freq.load(Ordering::Relaxed)) + } + fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { + if let Ok(r) = >::try_into(freq) { + match r { + FfroFreq::Ffro48m => { + // SAFETY: unsafe needed to take pointer to Clkctl0, needed to set the right HW frequency + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + clkctl0.ffroctl1().write(|w| w.update().update_safe_mode()); + clkctl0.ffroctl0().write(|w| w.trim_range().ffro_48mhz()); + clkctl0.ffroctl1().write(|w| w.update().normal_mode()); + + self.freq + .store(FfroFreq::Ffro48m as u32, core::sync::atomic::Ordering::Relaxed); + Ok(()) + } + FfroFreq::Ffro60m => { + // SAFETY: unsafe needed to take pointer to Clkctl0, needed to set the right HW frequency + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + clkctl0.ffroctl1().write(|w| w.update().update_safe_mode()); + clkctl0.ffroctl0().write(|w| w.trim_range().ffro_60mhz()); + clkctl0.ffroctl1().write(|w| w.update().normal_mode()); + + self.freq + .store(FfroFreq::Ffro60m as u32, core::sync::atomic::Ordering::Relaxed); + Ok(()) + } + } + } else { + error!("failed to convert desired clock rate, {:#}, to FFRO Freq", freq); + Err(ClockError::InvalidFrequency) + } + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } +} + +impl ConfigurableClock for SfroConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + // SAFETY: unsafe needed to take pointer to Sysctl0, only to power up SFRO + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + sysctl0.pdruncfg0_clr().write(|w| w.sfro_pd().clr_pdruncfg0()); + // wait until ready + while !sysctl0.pdruncfg0().read().sfro_pd().is_enabled() {} + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + // SAFETY: unsafe needed to take pointer to Sysctl0, only to power down SFRO + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + sysctl0.pdruncfg0_set().write(|w| w.sfro_pd().set_pdruncfg0()); + delay_loop_clocks(30, 12_000_000); + // Wait until SFRO disabled + while !sysctl0.pdruncfg0().read().sfro_pd().is_power_down() {} + Ok(()) + } + fn get_clock_rate(&self) -> Result { + if self.state == State::Enabled { + Ok(SFRO_FREQ) + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { + if self.state == State::Enabled { + if freq == SFRO_FREQ { + Ok(()) + } else { + Err(ClockError::InvalidFrequency) + } + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } +} + +/// A Clock with multiple options for clock source +pub trait MultiSourceClock { + /// Returns which clock is being used as the clock source and its rate + fn get_clock_source_and_rate(&self, clock: &Clocks) -> Result<(Clocks, u32), ClockError>; + /// Sets a specific clock source and its associated rate + fn set_clock_source_and_rate( + &mut self, + clock_src_config: &mut impl ConfigurableClock, + clock_src: &Clocks, + rate: u32, + ) -> Result<(), ClockError>; +} + +impl MultiSourceClock for MainPllClkConfig { + fn get_clock_source_and_rate(&self, clock: &Clocks) -> Result<(Clocks, u32), ClockError> { + match clock { + Clocks::MainPllClk => { + let converted_clock = Clocks::from(self.src); + Ok((converted_clock, self.freq.load(Ordering::Relaxed))) + } + _ => Err(ClockError::ClockMismatch), + } + } + fn set_clock_source_and_rate( + &mut self, + clock_src_config: &mut impl ConfigurableClock, + clock_src: &Clocks, + rate: u32, + ) -> Result<(), ClockError> { + if let Ok(c) = >::try_into(*clock_src) { + match c { + MainPllClkSrc::ClkIn => { + self.src = MainPllClkSrc::ClkIn; + // div mult and rate don't matter since this is an external clock + self.set_clock_rate(1, 1, rate) + } + MainPllClkSrc::FFRO => { + // FFRO Clock is divided by 2 + let r = clock_src_config.get_clock_rate()?; + let base_rate = r / 2; + let m = MainPllClkConfig::calc_mult(rate, base_rate)?; + + self.src = MainPllClkSrc::FFRO; + self.set_clock_rate(2, m, rate) + } + MainPllClkSrc::SFRO => { + if !clock_src_config.is_enabled() { + return Err(ClockError::ClockNotEnabled); + } + // check if desired frequency is a valid multiple of 16m SFRO clock + let m = MainPllClkConfig::calc_mult(rate, SFRO_FREQ)?; + self.src = MainPllClkSrc::SFRO; + self.set_clock_rate(1, m, rate) + } + } + } else { + Err(ClockError::ClockNotSupported) + } + } +} + +impl ConfigurableClock for MainPllClkConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + MainPllClkConfig::init_syspll(); + + MainPllClkConfig::init_syspll_pfd0(self.pfd0); + + MainPllClkConfig::init_syspll_pfd2(self.pfd2); + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + if self.is_enabled() { + Err(ClockError::ClockNotSupported) + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn get_clock_rate(&self) -> Result { + if self.is_enabled() { + let (_c, rate) = self.get_clock_source_and_rate(&Clocks::MainPllClk)?; + Ok(rate) + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn set_clock_rate(&mut self, div: u8, mult: u8, freq: u32) -> Result<(), ClockError> { + if self.is_enabled() { + // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0 + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + + // Power down pll before changes + sysctl0 + .pdruncfg0_set() + .write(|w| w.syspllldo_pd().set_pdruncfg0().syspllana_pd().set_pdruncfg0()); + + let desired_freq: u64 = self.freq.load(Ordering::Relaxed).into(); + + match self.src { + c if c == MainPllClkSrc::ClkIn || c == MainPllClkSrc::FFRO || c == MainPllClkSrc::SFRO => { + let mut base_rate; + match c { + MainPllClkSrc::ClkIn => { + clkctl0.syspll0clksel().write(|w| w.sel().sysxtal_clk()); + let r = self.get_clock_rate()?; + base_rate = r; + } + MainPllClkSrc::FFRO => { + delay_loop_clocks(1000, desired_freq); + match clkctl0.ffroctl0().read().trim_range().is_ffro_48mhz() { + true => base_rate = Into::into(FfroFreq::Ffro48m), + false => base_rate = Into::into(FfroFreq::Ffro60m), + } + if div == 2 { + clkctl0.syspll0clksel().write(|w| w.sel().ffro_div_2()); + delay_loop_clocks(150, desired_freq); + base_rate /= 2; + } else { + return Err(ClockError::InvalidDiv); + } + } + MainPllClkSrc::SFRO => { + base_rate = SFRO_FREQ; + clkctl0.syspll0clksel().write(|w| w.sel().sfro_clk()); + } + }; + base_rate *= u32::from(mult); + if base_rate != freq { + // make sure to power syspll back up before returning the error + // Clear System PLL reset + clkctl0.syspll0ctl0().write(|w| w.reset().normal()); + // Power up SYSPLL + sysctl0 + .pdruncfg0_clr() + .write(|w| w.syspllana_pd().clr_pdruncfg0().syspllldo_pd().clr_pdruncfg0()); + return Err(ClockError::InvalidFrequency); + } + // SAFETY: unsafe needed to write the bits for the num and demon fields + clkctl0.syspll0num().write(|w| unsafe { w.num().bits(0b0) }); + clkctl0.syspll0denom().write(|w| unsafe { w.denom().bits(0b1) }); + delay_loop_clocks(30, desired_freq); + self.mult.store(mult, Ordering::Relaxed); + match mult { + 16 => { + clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_16()); + } + 17 => { + clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_17()); + } + 20 => { + clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_20()); + } + 22 => { + clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_22()); + } + 27 => { + clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_27()); + } + 33 => { + clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_33()); + } + _ => return Err(ClockError::InvalidMult), + } + // Clear System PLL reset + clkctl0.syspll0ctl0().modify(|_r, w| w.reset().normal()); + // Power up SYSPLL + sysctl0 + .pdruncfg0_clr() + .write(|w| w.syspllana_pd().clr_pdruncfg0().syspllldo_pd().clr_pdruncfg0()); + + // Set System PLL HOLDRINGOFF_ENA + clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().enable()); + delay_loop_clocks(75, desired_freq); + + // Clear System PLL HOLDRINGOFF_ENA + clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().dsiable()); + delay_loop_clocks(15, desired_freq); + + // gate the output and clear bits. + // SAFETY: unsafe needed to write the bits for pfd0 + clkctl0 + .syspll0pfd() + .modify(|_, w| unsafe { w.pfd0_clkgate().gated().pfd0().bits(0x0) }); + // set pfd bits and un-gate the clock output + // output is multiplied by syspll * 18/pfd0_bits + // SAFETY: unsafe needed to write the bits for pfd0 + clkctl0 + .syspll0pfd() + .modify(|_r, w| unsafe { w.pfd0_clkgate().not_gated().pfd0().bits(0x12) }); + // wait for ready bit to be set + delay_loop_clocks(50, desired_freq); + while clkctl0.syspll0pfd().read().pfd0_clkrdy().bit_is_clear() {} + // clear by writing a 1 + clkctl0.syspll0pfd().modify(|_, w| w.pfd0_clkrdy().set_bit()); + + Ok(()) + } + _ => Err(ClockError::ClockNotSupported), + } + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } +} + +impl MainPllClkConfig { + /// Calculate the mult value of a desired frequency, return error if invalid + pub(self) fn calc_mult(rate: u32, base_freq: u32) -> Result { + const VALIDMULTS: [u8; 6] = [16, 17, 20, 22, 27, 33]; + if rate > base_freq && rate % base_freq == 0 { + let mult = (rate / base_freq) as u8; + if VALIDMULTS.into_iter().any(|i| i == mult) { + Ok(mult) + } else { + Err(ClockError::InvalidFrequency) + } + } else { + Err(ClockError::InvalidFrequency) + } + } + pub(self) fn init_syspll() { + // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0 + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + + // Power down SYSPLL before change fractional settings + sysctl0 + .pdruncfg0_set() + .write(|w| w.syspllldo_pd().set_pdruncfg0().syspllana_pd().set_pdruncfg0()); + + clkctl0.syspll0clksel().write(|w| w.sel().ffro_div_2()); + // SAFETY: unsafe needed to write the bits for both num and denom + clkctl0.syspll0num().write(|w| unsafe { w.num().bits(0x0) }); + clkctl0.syspll0denom().write(|w| unsafe { w.denom().bits(0x1) }); + + // kCLOCK_SysPllMult22 + clkctl0.syspll0ctl0().modify(|_, w| w.mult().div_22()); + + // Clear System PLL reset + clkctl0.syspll0ctl0().modify(|_, w| w.reset().normal()); + + // Power up SYSPLL + sysctl0 + .pdruncfg0_clr() + .write(|w| w.syspllldo_pd().clr_pdruncfg0().syspllana_pd().clr_pdruncfg0()); + delay_loop_clocks((150 & 0xFFFF) / 2, 12_000_000); + + // Set System PLL HOLDRINGOFF_ENA + clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().enable()); + delay_loop_clocks((150 & 0xFFFF) / 2, 12_000_000); + + // Clear System PLL HOLDRINGOFF_ENA + clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().dsiable()); + delay_loop_clocks((15 & 0xFFFF) / 2, 12_000_000); + } + /// enables default settings for pfd2 bits + pub(self) fn init_syspll_pfd2(config_bits: u8) { + // SAFETY: unsafe needed to take pointer to Clkctl0 and write specific bits + // needed to change the output of pfd0 + unsafe { + let clkctl0 = crate::pac::Clkctl0::steal(); + + // Disable the clock output first. + // SAFETY: unsafe needed to write the bits for pfd2 + clkctl0 + .syspll0pfd() + .modify(|_, w| w.pfd2_clkgate().gated().pfd2().bits(0x0)); + + // Set the new value and enable output. + // SAFETY: unsafe needed to write the bits for pfd2 + clkctl0 + .syspll0pfd() + .modify(|_, w| w.pfd2_clkgate().not_gated().pfd2().bits(config_bits)); + + // Wait for output becomes stable. + while clkctl0.syspll0pfd().read().pfd2_clkrdy().bit_is_clear() {} + + // Clear ready status flag. + clkctl0.syspll0pfd().modify(|_, w| w.pfd2_clkrdy().clear_bit()); + } + } + /// Enables default settings for pfd0 + pub(self) fn init_syspll_pfd0(config_bits: u8) { + // SAFETY: unsafe needed to take pointer to Clkctl0 and write specific bits + // needed to change the output of pfd0 + unsafe { + let clkctl0 = crate::pac::Clkctl0::steal(); + // Disable the clock output first + clkctl0 + .syspll0pfd() + .modify(|_, w| w.pfd0_clkgate().gated().pfd0().bits(0x0)); + + // Set the new value and enable output + clkctl0 + .syspll0pfd() + .modify(|_, w| w.pfd0_clkgate().not_gated().pfd0().bits(config_bits)); + + // Wait for output becomes stable + while clkctl0.syspll0pfd().read().pfd0_clkrdy().bit_is_clear() {} + + // Clear ready status flag + clkctl0.syspll0pfd().modify(|_, w| w.pfd0_clkrdy().clear_bit()); + } + } +} + +impl MainClkConfig { + fn init_main_clk() { + // SAFETY:: unsafe needed to take pointers to Clkctl0 and Clkctl1 + // used to set the right HW frequency + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; + + clkctl0.mainclkselb().write(|w| w.sel().main_pll_clk()); + + // Set PFC0DIV divider to value 2, Subtract 1 since 0-> 1, 1-> 2, etc... + clkctl0.pfcdiv(0).modify(|_, w| w.reset().set_bit()); + // SAFETY: unsafe needed to write the bits for pfcdiv + clkctl0 + .pfcdiv(0) + .write(|w| unsafe { w.div().bits(2 - 1).halt().clear_bit() }); + while clkctl0.pfcdiv(0).read().reqflag().bit_is_set() {} + + // Set FRGPLLCLKDIV divider to value 12, Subtract 1 since 0-> 1, 1-> 2, etc... + clkctl1.frgpllclkdiv().modify(|_, w| w.reset().set_bit()); + // SAFETY: unsafe needed to write the bits for frgpllclkdiv + clkctl1 + .frgpllclkdiv() + .write(|w| unsafe { w.div().bits(12 - 1).halt().clear_bit() }); + while clkctl1.frgpllclkdiv().read().reqflag().bit_is_set() {} + } +} +impl MultiSourceClock for MainClkConfig { + fn get_clock_source_and_rate(&self, clock: &Clocks) -> Result<(Clocks, u32), ClockError> { + match clock { + Clocks::MainClk => { + let div: u32 = if self.src == MainClkSrc::FFROdiv4 { 4 } else { 1 }; + let converted_clock = Clocks::from(self.src); + match ConfigurableClock::get_clock_rate(self) { + Ok(_rate) => { + // SAFETY: unsafe needed to take pointer to Clkctl0 + // needed to calculate the clock rate from the bits written in the registers + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + if self.src == MainClkSrc::PllMain && clkctl0.syspll0ctl0().read().bypass().is_programmed_clk() + { + let mut temp; + temp = self.freq.load(Ordering::Relaxed) + * u32::from(clkctl0.syspll0ctl0().read().mult().bits()); + temp = (u64::from(temp) * 18 / u64::from(clkctl0.syspll0pfd().read().pfd0().bits())) as u32; + return Ok((converted_clock, temp)); + } + Ok((converted_clock, self.freq.load(Ordering::Relaxed) / div)) + } + Err(clk_err) => Err(clk_err), + } + } + _ => Err(ClockError::ClockMismatch), + } + } + fn set_clock_source_and_rate( + &mut self, + clock_src_config: &mut impl ConfigurableClock, + clock_src: &Clocks, + rate: u32, + ) -> Result<(), ClockError> { + if !clock_src_config.is_enabled() { + return Err(ClockError::ClockNotEnabled); + } + if let Ok(c) = >::try_into(*clock_src) { + // SAFETY: unsafe needed to take pointer to Clkctl0 + // needed to change the clock source + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + match c { + MainClkSrc::ClkIn => { + self.src = MainClkSrc::ClkIn; + + clkctl0.mainclksela().write(|w| w.sel().sysxtal_clk()); + clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); + Ok(()) + } + // the following will yield the same result as if compared to FFROdiv4 + MainClkSrc::FFRO | MainClkSrc::FFROdiv4 => match rate { + div4 if div4 == (FfroFreq::Ffro60m as u32) / 4 || div4 == (FfroFreq::Ffro48m as u32) / 4 => { + self.src = MainClkSrc::FFROdiv4; + self.freq.store(div4, Ordering::Relaxed); + + clkctl0.mainclksela().write(|w| w.sel().ffro_div_4()); + clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); + Ok(()) + } + div1 if div1 == FfroFreq::Ffro60m as u32 || div1 == FfroFreq::Ffro48m as u32 => { + self.src = MainClkSrc::FFRO; + self.freq.store(div1, Ordering::Relaxed); + + clkctl0.mainclksela().write(|w| w.sel().ffro_clk()); + clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); + Ok(()) + } + _ => Err(ClockError::InvalidFrequency), + }, + MainClkSrc::Lposc => { + if let Ok(r) = >::try_into(rate) { + match r { + LposcFreq::Lp1m => { + self.src = MainClkSrc::Lposc; + self.freq.store(rate, Ordering::Relaxed); + + clkctl0.mainclksela().write(|w| w.sel().lposc()); + clkctl0.mainclkselb().write(|w| w.sel().main_1st_clk()); + Ok(()) + } + LposcFreq::Lp32k => Err(ClockError::InvalidFrequency), + } + } else { + Err(ClockError::InvalidFrequency) + } + } + MainClkSrc::SFRO => { + if rate == SFRO_FREQ { + self.src = MainClkSrc::SFRO; + self.freq.store(rate, Ordering::Relaxed); + clkctl0.mainclkselb().write(|w| w.sel().sfro_clk()); + Ok(()) + } else { + Err(ClockError::InvalidFrequency) + } + } + MainClkSrc::PllMain => { + let r = rate; + // From Section 4.6.1.1 Pll Limitations of the RT6xx User manual + let pll_max = 572_000_000; + let pll_min = 80_000_000; + if pll_min <= r && r <= pll_max { + clkctl0.mainclkselb().write(|w| w.sel().main_pll_clk()); + self.src = MainClkSrc::PllMain; + self.freq.store(r, Ordering::Relaxed); + Ok(()) + } else { + Err(ClockError::InvalidFrequency) + } + } + MainClkSrc::RTC32k => { + if rate == RtcFreq::SubSecond32kHz as u32 { + self.src = MainClkSrc::RTC32k; + self.freq.store(rate, Ordering::Relaxed); + clkctl0.mainclkselb().write(|w| w.sel().rtc_32k_clk()); + Ok(()) + } else { + Err(ClockError::InvalidFrequency) + } + } + } + } else { + Err(ClockError::ClockNotSupported) + } + } +} + +impl ConfigurableClock for MainClkConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + MainClkConfig::init_main_clk(); + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + Err(ClockError::ClockNotSupported) + } + fn get_clock_rate(&self) -> Result { + let (_c, rate) = MainClkConfig::get_clock_source_and_rate(self, &Clocks::MainClk)?; + Ok(rate) + } + fn set_clock_rate(&mut self, _div: u8, _mult: u8, _freq: u32) -> Result<(), ClockError> { + Err(ClockError::ClockNotSupported) + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } +} + +impl ConfigurableClock for ClkInConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + // External Input, no hw writes needed + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + error!("Attempting to reset a clock input"); + Err(ClockError::ClockNotSupported) + } + fn get_clock_rate(&self) -> Result { + if self.freq.is_some() { + Ok(self.freq.as_ref().unwrap().load(Ordering::Relaxed)) + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { + self.freq.as_ref().unwrap().store(freq, Ordering::Relaxed); + Ok(()) + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } +} + +impl RtcClkConfig { + /// Register writes to initialize the RTC Clock + fn init_rtc_clk() { + // SAFETY: unsafe needed to take pointer to Clkctl0, Clkctl1, and RTC + // needed to enable the RTC HW + let cc0 = unsafe { pac::Clkctl0::steal() }; + let cc1 = unsafe { pac::Clkctl1::steal() }; + let r = unsafe { pac::Rtc::steal() }; + // Enable the RTC peripheral clock + cc1.pscctl2_set().write(|w| w.rtc_lite_clk_set().set_clock()); + // Make sure the reset bit is cleared amd RTC OSC is powered up + r.ctrl().modify(|_, w| w.swreset().not_in_reset().rtc_osc_pd().enable()); + + // set initial match value, note that with a 15 bit count-down timer this would + // typically be 0x8000, but we are "doing some clever things" in time-driver.rs, + // read more about it in the comments there + // SAFETY: unsafe needed to write the bits + r.wake().write(|w| unsafe { w.bits(0xA) }); + + // Enable 32K OSC + cc0.osc32khzctl0().write(|w| w.ena32khz().enabled()); + + // enable rtc clk + r.ctrl().modify(|_, w| w.rtc_en().enable()); + } +} + +impl ConfigurableClock for RtcClkConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + // should only be called once if previously disabled + RtcClkConfig::init_rtc_clk(); + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + Err(ClockError::ClockNotSupported) + } + fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> { + if let Ok(r) = >::try_into(freq) { + // SAFETY: unsafe needed to take pointer to RTC + // needed to enable the HW for the different RTC frequencies, powered down by default + let rtc = unsafe { crate::pac::Rtc::steal() }; + match r { + RtcFreq::Default1Hz => { + if rtc.ctrl().read().rtc_en().is_enable() { + } else { + rtc.ctrl().modify(|_r, w| w.rtc_en().enable()); + } + Ok(()) + } + RtcFreq::HighResolution1khz => { + if rtc.ctrl().read().rtc1khz_en().is_enable() { + } else { + rtc.ctrl().modify(|_r, w| w.rtc1khz_en().enable()); + } + Ok(()) + } + RtcFreq::SubSecond32kHz => { + if rtc.ctrl().read().rtc_subsec_ena().is_enable() { + } else { + rtc.ctrl().modify(|_r, w| w.rtc_subsec_ena().enable()); + } + Ok(()) + } + } + } else { + Err(ClockError::InvalidFrequency) + } + } + // unlike the others, since this provides multiple clocks, return the fastest one + fn get_clock_rate(&self) -> Result { + if self.sub_second_state == State::Enabled { + Ok(RtcFreq::SubSecond32kHz as u32) + } else if self.wake_alarm_state == State::Enabled { + Ok(RtcFreq::HighResolution1khz as u32) + } else if self.state == State::Enabled { + Ok(RtcFreq::Default1Hz as u32) + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } +} + +impl SysClkConfig { + /// Updates the system core clock frequency, SW concept used for systick + fn update_sys_core_clock(&self) {} +} + +impl ConfigurableClock for SysOscConfig { + fn enable_and_reset(&self) -> Result<(), ClockError> { + if self.state == State::Enabled { + return Ok(()); + } + + // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0, needed to modify clock HW + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + + // Let CPU run on ffro for safe switching + clkctl0.mainclksela().write(|w| w.sel().ffro_clk()); + clkctl0.mainclksela().write(|w| w.sel().ffro_div_4()); + + // Power on SYSXTAL + sysctl0.pdruncfg0_clr().write(|w| w.sysxtal_pd().clr_pdruncfg0()); + + // Enable system OSC + clkctl0 + .sysoscctl0() + .write(|w| w.lp_enable().lp().bypass_enable().normal_mode()); + + delay_loop_clocks(260, SYS_OSC_DEFAULT_FREQ.into()); + Ok(()) + } + fn disable(&self) -> Result<(), ClockError> { + // SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0, needed to modify clock HW + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + let sysctl0 = unsafe { crate::pac::Sysctl0::steal() }; + + // Let CPU run on ffro for safe switching + clkctl0.mainclksela().write(|w| w.sel().ffro_clk()); + clkctl0.mainclksela().write(|w| w.sel().ffro_div_4()); + + // Power on SYSXTAL + sysctl0.pdruncfg0_set().write(|w| w.sysxtal_pd().set_pdruncfg0()); + Ok(()) + } + fn get_clock_rate(&self) -> Result { + if self.state == State::Enabled { + Ok(SYS_OSC_DEFAULT_FREQ) + } else { + Err(ClockError::ClockNotEnabled) + } + } + fn is_enabled(&self) -> bool { + self.state == State::Enabled + } + fn set_clock_rate(&mut self, _div: u8, _mult: u8, _freq: u32) -> Result<(), ClockError> { + Err(ClockError::ClockNotSupported) + } +} + +/// Method to delay for a certain number of microseconds given a clock rate +pub fn delay_loop_clocks(usec: u64, freq_mhz: u64) { + let mut ticks = usec * freq_mhz / 1_000_000 / 4; + if ticks > u64::from(u32::MAX) { + ticks = u64::from(u32::MAX); + } + // won't panic since we check value above + cortex_m::asm::delay(ticks as u32); +} + +/// Configure the pad voltage pmc registers for all 3 vddio ranges +fn set_pad_voltage_range() { + // SAFETY: unsafe needed to take pointer to PNC as well as to write specific bits + unsafe { + let pmc = crate::pac::Pmc::steal(); + // Set up IO voltages + // all 3 ranges need to be 1.71-1.98V which is 01 + pmc.padvrange().write(|w| { + w.vddio_0range() + .bits(0b01) + .vddio_1range() + .bits(0b01) + .vddio_2range() + .bits(0b01) + }); + } +} + +/// Initialize AHB clock +fn init_syscpuahb_clk() { + // SAFETY: unsafe needed to take pointer to Clkctl0 + let clkctl0 = unsafe { crate::pac::Clkctl0::steal() }; + // SAFETY: unsafe needed to write the bits + // Set syscpuahbclkdiv to value 2, Subtract 1 since 0-> 1, 1-> 2, etc... + clkctl0.syscpuahbclkdiv().write(|w| unsafe { w.div().bits(2 - 1) }); + + while clkctl0.syscpuahbclkdiv().read().reqflag().bit_is_set() {} +} + +/// `ClockOut` config +pub struct ClockOutConfig { + src: ClkOutSrc, + div: u8, +} + +/// `ClockOut` sources +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// `ClockOut` sources +pub enum ClkOutSrc { + /// No Source, reduce power consumption + None, + /// SFRO clock + Sfro, + /// External input clock + ClkIn, + /// Low-power oscillator + Lposc, + /// FFRO clock + Ffro, + /// Main clock + MainClk, + /// Main DSP clock + DspMainClk, + /// Main Pll clock + MainPllClk, + /// `SysPll` Aux0 clock + Aux0PllClk, + /// `SysPll` DSP clock + DspPllClk, + /// `SysPll` Aux1 clock + Aux1PllClk, + /// Audio Pll clock + AudioPllClk, + /// 32 `KHz` RTC + RTC32k, +} + +/// Initialize the `ClkOutConfig` +impl ClockOutConfig { + /// Default configuration for Clock out + #[must_use] + pub fn default_config() -> Self { + Self { + src: ClkOutSrc::None, + div: 0, + } + } + + /// Enable the Clock Out output + pub fn enable_and_reset(&mut self) -> Result<(), ClockError> { + self.set_clkout_source_and_div(self.src, self.div)?; + Ok(()) + } + + /// Disable Clock Out output and select None as the source to conserve power + pub fn disable(&mut self) -> Result<(), ClockError> { + self.set_clkout_source_and_div(ClkOutSrc::None, 0)?; + Ok(()) + } + + /// Set the source of the Clock Out pin + fn set_clkout_source(&mut self, src: ClkOutSrc) -> Result<(), ClockError> { + // SAFETY: unsafe needed to take pointers to Clkctl1, needed to set source in HW + let cc1 = unsafe { pac::Clkctl1::steal() }; + match src { + ClkOutSrc::None => { + cc1.clkoutsel0().write(|w| w.sel().none()); + cc1.clkoutsel1().write(|w| w.sel().none()); + } + ClkOutSrc::Sfro => { + cc1.clkoutsel0().write(|w| w.sel().sfro_clk()); + cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); + } + ClkOutSrc::ClkIn => { + cc1.clkoutsel0().write(|w| w.sel().xtalin_clk()); + cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); + } + ClkOutSrc::Lposc => { + cc1.clkoutsel0().write(|w| w.sel().lposc()); + cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); + } + ClkOutSrc::Ffro => { + cc1.clkoutsel0().write(|w| w.sel().ffro_clk()); + cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); + } + ClkOutSrc::MainClk => { + cc1.clkoutsel0().write(|w| w.sel().main_clk()); + cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); + } + ClkOutSrc::DspMainClk => { + cc1.clkoutsel0().write(|w| w.sel().dsp_main_clk()); + cc1.clkoutsel1().write(|w| w.sel().clkoutsel0_output()); + } + ClkOutSrc::MainPllClk => { + cc1.clkoutsel0().write(|w| w.sel().none()); + cc1.clkoutsel1().write(|w| w.sel().main_pll_clk()); + } + ClkOutSrc::Aux0PllClk => { + cc1.clkoutsel0().write(|w| w.sel().none()); + cc1.clkoutsel1().write(|w| w.sel().syspll0_aux0_pll_clk()); + } + ClkOutSrc::DspPllClk => { + cc1.clkoutsel0().write(|w| w.sel().none()); + cc1.clkoutsel1().write(|w| w.sel().dsp_pll_clk()); + } + ClkOutSrc::AudioPllClk => { + cc1.clkoutsel0().write(|w| w.sel().none()); + cc1.clkoutsel1().write(|w| w.sel().audio_pll_clk()); + } + ClkOutSrc::Aux1PllClk => { + cc1.clkoutsel0().write(|w| w.sel().none()); + cc1.clkoutsel1().write(|w| w.sel().syspll0_aux1_pll_clk()); + } + ClkOutSrc::RTC32k => { + cc1.clkoutsel0().write(|w| w.sel().none()); + cc1.clkoutsel1().write(|w| w.sel().rtc_clk_32khz()); + } + } + self.src = src; + Ok(()) + } + /// set the clock out divider + /// note that 1 will be added to div when mapping to the divider + /// so bits(0) -> divide by 1 + /// ... + /// bits(255)-> divide by 256 + pub fn set_clkout_divider(&self, div: u8) -> Result<(), ClockError> { + // don't wait for clock to be ready if there's no source + if self.src != ClkOutSrc::None { + let cc1 = unsafe { pac::Clkctl1::steal() }; + + cc1.clkoutdiv() + .modify(|_, w| unsafe { w.div().bits(div).halt().clear_bit() }); + while cc1.clkoutdiv().read().reqflag().bit_is_set() {} + } + Ok(()) + } + /// set the source and divider for the clockout pin + pub fn set_clkout_source_and_div(&mut self, src: ClkOutSrc, div: u8) -> Result<(), ClockError> { + self.set_clkout_source(src)?; + + self.set_clkout_divider(div)?; + + Ok(()) + } +} + +/// Using the config, enables all desired clocks to desired clock rates +fn init_clock_hw(config: ClockConfig) -> Result<(), ClockError> { + if let Err(e) = config.rtc.enable_and_reset() { + return Err(e); + } + + if let Err(e) = config.lposc.enable_and_reset() { + return Err(e); + } + + if let Err(e) = config.ffro.enable_and_reset() { + return Err(e); + } + + if let Err(e) = config.sfro.enable_and_reset() { + return Err(e); + } + + if let Err(e) = config.sys_osc.enable_and_reset() { + return Err(e); + } + + if let Err(e) = config.main_pll_clk.enable_and_reset() { + return Err(e); + } + + // Move FLEXSPI clock source from main clock to FFRO to avoid instruction/data fetch issue in XIP when + // updating PLL and main clock. + // SAFETY: unsafe needed to take pointers to Clkctl0 + let cc0 = unsafe { pac::Clkctl0::steal() }; + cc0.flexspifclksel().write(|w| w.sel().ffro_clk()); + + // Move ESPI clock source to FFRO + #[cfg(feature = "_espi")] + { + cc0.espiclksel().write(|w| w.sel().use_48_60m()); + } + + init_syscpuahb_clk(); + + if let Err(e) = config.main_clk.enable_and_reset() { + return Err(e); + } + + config.sys_clk.update_sys_core_clock(); + Ok(()) +} + +/// SAFETY: must be called exactly once at bootup +pub(crate) unsafe fn init(config: ClockConfig) -> Result<(), ClockError> { + init_clock_hw(config)?; + + // set VDDIO ranges 0-2 + set_pad_voltage_range(); + Ok(()) +} + +///Trait to expose perph clocks +trait SealedSysconPeripheral { + fn enable_perph_clock(); + fn reset_perph(); + fn disable_perph_clock(); +} + +/// Clock and Reset control for peripherals +#[allow(private_bounds)] +pub trait SysconPeripheral: SealedSysconPeripheral + 'static {} +/// Enables and resets peripheral `T`. +/// +/// # Safety +/// +/// Peripheral must not be in use. +pub fn enable_and_reset() { + T::enable_perph_clock(); + T::reset_perph(); +} + +/// Enables peripheral `T`. +pub fn enable() { + T::enable_perph_clock(); +} + +/// Reset peripheral `T`. +pub fn reset() { + T::reset_perph(); +} + +/// Disables peripheral `T`. +/// +/// # Safety +/// +/// Peripheral must not be in use. +pub fn disable() { + T::disable_perph_clock(); +} +macro_rules! impl_perph_clk { + ($peripheral:ident, $clkctl:ident, $clkreg:ident, $rstctl:ident, $rstreg:ident, $bit:expr) => { + impl SealedSysconPeripheral for crate::peripherals::$peripheral { + fn enable_perph_clock() { + // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 + let cc1 = unsafe { pac::$clkctl::steal() }; + + paste! { + // SAFETY: unsafe due to the use of bits() + cc1.[<$clkreg _set>]().write(|w| unsafe { w.bits(1 << $bit) }); + } + } + + fn reset_perph() { + // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 + let rc1 = unsafe { pac::$rstctl::steal() }; + + paste! { + // SAFETY: unsafe due to the use of bits() + rc1.[<$rstreg _clr>]().write(|w| unsafe { w.bits(1 << $bit) }); + } + } + + fn disable_perph_clock() { + // SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1 + let cc1 = unsafe { pac::$clkctl::steal() }; + + paste! { + // SAFETY: unsafe due to the use of bits() + cc1.[<$clkreg _clr>]().write(|w| unsafe { w.bits(1 << $bit) }); + } + } + } + + impl SysconPeripheral for crate::peripherals::$peripheral {} + }; +} + +// These should enabled once the relevant peripherals are implemented. +// impl_perph_clk!(GPIOINTCTL, Clkctl1, pscctl2, Rstctl1, prstctl2, 30); +// impl_perph_clk!(OTP, Clkctl0, pscctl0, Rstctl0, prstctl0, 17); + +// impl_perph_clk!(ROM_CTL_128KB, Clkctl0, pscctl0, Rstctl0, prstctl0, 2); +// impl_perph_clk!(USBHS_SRAM, Clkctl0, pscctl0, Rstctl0, prstctl0, 23); + +impl_perph_clk!(PIMCTL, Clkctl1, pscctl2, Rstctl1, prstctl2, 31); +impl_perph_clk!(ACMP, Clkctl0, pscctl1, Rstctl0, prstctl1, 15); +impl_perph_clk!(ADC0, Clkctl0, pscctl1, Rstctl0, prstctl1, 16); +impl_perph_clk!(CASPER, Clkctl0, pscctl0, Rstctl0, prstctl0, 9); +impl_perph_clk!(CRC, Clkctl1, pscctl1, Rstctl1, prstctl1, 16); +impl_perph_clk!(CTIMER0_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 0); +impl_perph_clk!(CTIMER1_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 1); +impl_perph_clk!(CTIMER2_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 2); +impl_perph_clk!(CTIMER3_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 3); +impl_perph_clk!(CTIMER4_COUNT_CHANNEL0, Clkctl1, pscctl2, Rstctl1, prstctl2, 4); +impl_perph_clk!(DMA0, Clkctl1, pscctl1, Rstctl1, prstctl1, 23); +impl_perph_clk!(DMA1, Clkctl1, pscctl1, Rstctl1, prstctl1, 24); +impl_perph_clk!(DMIC0, Clkctl1, pscctl0, Rstctl1, prstctl0, 24); + +#[cfg(feature = "_espi")] +impl_perph_clk!(ESPI, Clkctl0, pscctl1, Rstctl0, prstctl1, 7); + +impl_perph_clk!(FLEXCOMM0, Clkctl1, pscctl0, Rstctl1, prstctl0, 8); +impl_perph_clk!(FLEXCOMM1, Clkctl1, pscctl0, Rstctl1, prstctl0, 9); +impl_perph_clk!(FLEXCOMM14, Clkctl1, pscctl0, Rstctl1, prstctl0, 22); +impl_perph_clk!(FLEXCOMM15, Clkctl1, pscctl0, Rstctl1, prstctl0, 23); +impl_perph_clk!(FLEXCOMM2, Clkctl1, pscctl0, Rstctl1, prstctl0, 10); +impl_perph_clk!(FLEXCOMM3, Clkctl1, pscctl0, Rstctl1, prstctl0, 11); +impl_perph_clk!(FLEXCOMM4, Clkctl1, pscctl0, Rstctl1, prstctl0, 12); +impl_perph_clk!(FLEXCOMM5, Clkctl1, pscctl0, Rstctl1, prstctl0, 13); +impl_perph_clk!(FLEXCOMM6, Clkctl1, pscctl0, Rstctl1, prstctl0, 14); +impl_perph_clk!(FLEXCOMM7, Clkctl1, pscctl0, Rstctl1, prstctl0, 15); +impl_perph_clk!(FLEXSPI, Clkctl0, pscctl0, Rstctl0, prstctl0, 16); +impl_perph_clk!(FREQME, Clkctl1, pscctl1, Rstctl1, prstctl1, 31); +impl_perph_clk!(HASHCRYPT, Clkctl0, pscctl0, Rstctl0, prstctl0, 10); +impl_perph_clk!(HSGPIO0, Clkctl1, pscctl1, Rstctl1, prstctl1, 0); +impl_perph_clk!(HSGPIO1, Clkctl1, pscctl1, Rstctl1, prstctl1, 1); +impl_perph_clk!(HSGPIO2, Clkctl1, pscctl1, Rstctl1, prstctl1, 2); +impl_perph_clk!(HSGPIO3, Clkctl1, pscctl1, Rstctl1, prstctl1, 3); +impl_perph_clk!(HSGPIO4, Clkctl1, pscctl1, Rstctl1, prstctl1, 4); +impl_perph_clk!(HSGPIO5, Clkctl1, pscctl1, Rstctl1, prstctl1, 5); +impl_perph_clk!(HSGPIO6, Clkctl1, pscctl1, Rstctl1, prstctl1, 6); +impl_perph_clk!(HSGPIO7, Clkctl1, pscctl1, Rstctl1, prstctl1, 7); +impl_perph_clk!(I3C0, Clkctl1, pscctl2, Rstctl1, prstctl2, 16); +impl_perph_clk!(MRT0, Clkctl1, pscctl2, Rstctl1, prstctl2, 8); +impl_perph_clk!(MU_A, Clkctl1, pscctl1, Rstctl1, prstctl1, 28); +impl_perph_clk!(OS_EVENT, Clkctl1, pscctl0, Rstctl1, prstctl0, 27); +impl_perph_clk!(POWERQUAD, Clkctl0, pscctl0, Rstctl0, prstctl0, 8); +impl_perph_clk!(PUF, Clkctl0, pscctl0, Rstctl0, prstctl0, 11); +impl_perph_clk!(RNG, Clkctl0, pscctl0, Rstctl0, prstctl0, 12); +impl_perph_clk!(RTC, Clkctl1, pscctl2, Rstctl1, prstctl2, 7); +impl_perph_clk!(SCT0, Clkctl0, pscctl0, Rstctl0, prstctl0, 24); +impl_perph_clk!(SECGPIO, Clkctl0, pscctl1, Rstctl0, prstctl1, 24); +impl_perph_clk!(SEMA42, Clkctl1, pscctl1, Rstctl1, prstctl1, 29); +impl_perph_clk!(USBHSD, Clkctl0, pscctl0, Rstctl0, prstctl0, 21); +impl_perph_clk!(USBHSH, Clkctl0, pscctl0, Rstctl0, prstctl0, 22); +impl_perph_clk!(USBPHY, Clkctl0, pscctl0, Rstctl0, prstctl0, 20); +impl_perph_clk!(USDHC0, Clkctl0, pscctl1, Rstctl0, prstctl1, 2); +impl_perph_clk!(USDHC1, Clkctl0, pscctl1, Rstctl0, prstctl1, 3); +impl_perph_clk!(UTICK0, Clkctl0, pscctl2, Rstctl0, prstctl2, 0); +impl_perph_clk!(WDT0, Clkctl0, pscctl2, Rstctl0, prstctl2, 1); +impl_perph_clk!(WDT1, Clkctl1, pscctl2, Rstctl1, prstctl2, 10); diff --git a/embassy-imxrt/src/crc.rs b/embassy-imxrt/src/crc.rs new file mode 100644 index 000000000..24d6ba5bd --- /dev/null +++ b/embassy-imxrt/src/crc.rs @@ -0,0 +1,190 @@ +//! Cyclic Redundancy Check (CRC) + +use core::marker::PhantomData; + +use crate::clocks::{enable_and_reset, SysconPeripheral}; +pub use crate::pac::crc_engine::mode::CrcPolynomial as Polynomial; +use crate::{peripherals, Peri, PeripheralType}; + +/// CRC driver. +pub struct Crc<'d> { + info: Info, + _config: Config, + _lifetime: PhantomData<&'d ()>, +} + +/// CRC configuration +pub struct Config { + /// Polynomial to be used + pub polynomial: Polynomial, + + /// Reverse bit order of input? + pub reverse_in: bool, + + /// 1's complement input? + pub complement_in: bool, + + /// Reverse CRC bit order? + pub reverse_out: bool, + + /// 1's complement CRC? + pub complement_out: bool, + + /// CRC Seed + pub seed: u32, +} + +impl Config { + /// Create a new CRC config. + #[must_use] + pub fn new( + polynomial: Polynomial, + reverse_in: bool, + complement_in: bool, + reverse_out: bool, + complement_out: bool, + seed: u32, + ) -> Self { + Config { + polynomial, + reverse_in, + complement_in, + reverse_out, + complement_out, + seed, + } + } +} + +impl Default for Config { + fn default() -> Self { + Self { + polynomial: Polynomial::CrcCcitt, + reverse_in: false, + complement_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff, + } + } +} + +impl<'d> Crc<'d> { + /// Instantiates new CRC peripheral and initializes to default values. + pub fn new(_peripheral: Peri<'d, T>, config: Config) -> Self { + // enable CRC clock + enable_and_reset::(); + + let mut instance = Self { + info: T::info(), + _config: config, + _lifetime: PhantomData, + }; + + instance.reconfigure(); + instance + } + + /// Reconfigured the CRC peripheral. + fn reconfigure(&mut self) { + self.info.regs.mode().write(|w| { + w.crc_poly() + .variant(self._config.polynomial) + .bit_rvs_wr() + .variant(self._config.reverse_in) + .cmpl_wr() + .variant(self._config.complement_in) + .bit_rvs_sum() + .variant(self._config.reverse_out) + .cmpl_sum() + .variant(self._config.complement_out) + }); + + // Init CRC value + self.info + .regs + .seed() + .write(|w| unsafe { w.crc_seed().bits(self._config.seed) }); + } + + /// Feeds a byte into the CRC peripheral. Returns the computed checksum. + pub fn feed_byte(&mut self, byte: u8) -> u32 { + self.info.regs.wr_data8().write(|w| unsafe { w.bits(byte) }); + + self.info.regs.sum().read().bits() + } + + /// Feeds an slice of bytes into the CRC peripheral. Returns the computed checksum. + pub fn feed_bytes(&mut self, bytes: &[u8]) -> u32 { + let (prefix, data, suffix) = unsafe { bytes.align_to::() }; + + for b in prefix { + self.info.regs.wr_data8().write(|w| unsafe { w.bits(*b) }); + } + + for d in data { + self.info.regs.wr_data32().write(|w| unsafe { w.bits(*d) }); + } + + for b in suffix { + self.info.regs.wr_data8().write(|w| unsafe { w.bits(*b) }); + } + + self.info.regs.sum().read().bits() + } + + /// Feeds a halfword into the CRC peripheral. Returns the computed checksum. + pub fn feed_halfword(&mut self, halfword: u16) -> u32 { + self.info.regs.wr_data16().write(|w| unsafe { w.bits(halfword) }); + + self.info.regs.sum().read().bits() + } + + /// Feeds an slice of halfwords into the CRC peripheral. Returns the computed checksum. + pub fn feed_halfwords(&mut self, halfwords: &[u16]) -> u32 { + for halfword in halfwords { + self.info.regs.wr_data16().write(|w| unsafe { w.bits(*halfword) }); + } + + self.info.regs.sum().read().bits() + } + + /// Feeds a words into the CRC peripheral. Returns the computed checksum. + pub fn feed_word(&mut self, word: u32) -> u32 { + self.info.regs.wr_data32().write(|w| unsafe { w.bits(word) }); + + self.info.regs.sum().read().bits() + } + + /// Feeds an slice of words into the CRC peripheral. Returns the computed checksum. + pub fn feed_words(&mut self, words: &[u32]) -> u32 { + for word in words { + self.info.regs.wr_data32().write(|w| unsafe { w.bits(*word) }); + } + + self.info.regs.sum().read().bits() + } +} + +struct Info { + regs: crate::pac::CrcEngine, +} + +trait SealedInstance { + fn info() -> Info; +} + +/// CRC instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + SysconPeripheral + 'static + Send {} + +impl Instance for peripherals::CRC {} + +impl SealedInstance for peripherals::CRC { + fn info() -> Info { + // SAFETY: safe from single executor + Info { + regs: unsafe { crate::pac::CrcEngine::steal() }, + } + } +} diff --git a/embassy-imxrt/src/dma.rs b/embassy-imxrt/src/dma.rs new file mode 100644 index 000000000..e141447f3 --- /dev/null +++ b/embassy-imxrt/src/dma.rs @@ -0,0 +1,418 @@ +//! DMA driver. + +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::dma0::channel::cfg::Periphreqen; +use pac::dma0::channel::xfercfg::{Dstinc, Srcinc, Width}; + +use crate::clocks::enable_and_reset; +use crate::interrupt::InterruptExt; +use crate::peripherals::DMA0; +use crate::sealed::Sealed; +use crate::{interrupt, pac, peripherals, BitIter}; + +#[cfg(feature = "rt")] +#[interrupt] +fn DMA0() { + let reg = unsafe { crate::pac::Dma0::steal() }; + + if reg.intstat().read().activeerrint().bit() { + let err = reg.errint0().read().bits(); + + for channel in BitIter(err) { + error!("DMA error interrupt on channel {}!", channel); + reg.errint0().write(|w| unsafe { w.err().bits(1 << channel) }); + CHANNEL_WAKERS[channel as usize].wake(); + } + } + + if reg.intstat().read().activeint().bit() { + let ia = reg.inta0().read().bits(); + + for channel in BitIter(ia) { + reg.inta0().write(|w| unsafe { w.ia().bits(1 << channel) }); + CHANNEL_WAKERS[channel as usize].wake(); + } + } +} + +/// Initialize DMA controllers (DMA0 only, for now) +pub(crate) unsafe fn init() { + let sysctl0 = crate::pac::Sysctl0::steal(); + let dmactl0 = crate::pac::Dma0::steal(); + + enable_and_reset::(); + + interrupt::DMA0.disable(); + interrupt::DMA0.set_priority(interrupt::Priority::P3); + + dmactl0.ctrl().modify(|_, w| w.enable().set_bit()); + + // Set channel descriptor SRAM base address + // Descriptor base must be 1K aligned + let descriptor_base = core::ptr::addr_of!(DESCRIPTORS.descs) as u32; + dmactl0.srambase().write(|w| w.bits(descriptor_base)); + + // Ensure AHB priority it highest (M4 == DMAC0) + sysctl0.ahbmatrixprior().modify(|_, w| w.m4().bits(0)); + + interrupt::DMA0.unpend(); + interrupt::DMA0.enable(); +} + +/// DMA read. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn read<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: *const W, to: *mut [W]) -> Transfer<'a, C> { + let count = ((to.len() / W::size() as usize) - 1) as isize; + + copy_inner( + ch, + from as *const u32, + (to as *mut u32).byte_offset(count * W::size()), + W::width(), + count, + false, + true, + true, + ) +} + +/// DMA write. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn write<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: *const [W], to: *mut W) -> Transfer<'a, C> { + let count = ((from.len() / W::size() as usize) - 1) as isize; + + copy_inner( + ch, + (from as *const u32).byte_offset(count * W::size()), + to as *mut u32, + W::width(), + count, + true, + false, + true, + ) +} + +/// DMA copy between slices. +/// +/// SAFETY: Slices must point to locations reachable by DMA. +pub unsafe fn copy<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: &[W], to: &mut [W]) -> Transfer<'a, C> { + let from_len = from.len(); + let to_len = to.len(); + assert_eq!(from_len, to_len); + + let count = ((from_len / W::size() as usize) - 1) as isize; + + copy_inner( + ch, + from.as_ptr().byte_offset(count * W::size()) as *const u32, + to.as_mut_ptr().byte_offset(count * W::size()) as *mut u32, + W::width(), + count, + true, + true, + false, + ) +} + +fn copy_inner<'a, C: Channel>( + ch: Peri<'a, C>, + from: *const u32, + to: *mut u32, + width: Width, + count: isize, + incr_read: bool, + incr_write: bool, + periph: bool, +) -> Transfer<'a, C> { + let p = ch.regs(); + + unsafe { + DESCRIPTORS.descs[ch.number() as usize].src = from as u32; + DESCRIPTORS.descs[ch.number() as usize].dest = to as u32; + } + + compiler_fence(Ordering::SeqCst); + + p.errint0().write(|w| unsafe { w.err().bits(1 << ch.number()) }); + p.inta0().write(|w| unsafe { w.ia().bits(1 << ch.number()) }); + + p.channel(ch.number().into()).cfg().write(|w| { + unsafe { w.chpriority().bits(0) } + .periphreqen() + .variant(match periph { + false => Periphreqen::Disabled, + true => Periphreqen::Enabled, + }) + .hwtrigen() + .clear_bit() + }); + + p.intenset0().write(|w| unsafe { w.inten().bits(1 << ch.number()) }); + + p.channel(ch.number().into()).xfercfg().write(|w| { + unsafe { w.xfercount().bits(count as u16) } + .cfgvalid() + .set_bit() + .clrtrig() + .set_bit() + .reload() + .clear_bit() + .setinta() + .set_bit() + .width() + .variant(width) + .srcinc() + .variant(match incr_read { + false => Srcinc::NoIncrement, + true => Srcinc::WidthX1, + // REVISIT: what about WidthX2 and WidthX4? + }) + .dstinc() + .variant(match incr_write { + false => Dstinc::NoIncrement, + true => Dstinc::WidthX1, + // REVISIT: what about WidthX2 and WidthX4? + }) + }); + + p.enableset0().write(|w| unsafe { w.ena().bits(1 << ch.number()) }); + + p.channel(ch.number().into()) + .xfercfg() + .modify(|_, w| w.swtrig().set_bit()); + + compiler_fence(Ordering::SeqCst); + + Transfer::new(ch) +} + +/// DMA transfer driver. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: Peri<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub(crate) fn new(channel: Peri<'a, C>) -> Self { + Self { channel } + } + + pub(crate) fn abort(&mut self) -> usize { + let p = self.channel.regs(); + + p.abort0().write(|w| w.channel(self.channel.number()).set_bit()); + while p.busy0().read().bsy().bits() & (1 << self.channel.number()) != 0 {} + + p.enableclr0() + .write(|w| unsafe { w.clr().bits(1 << self.channel.number()) }); + + let width: u8 = p + .channel(self.channel.number().into()) + .xfercfg() + .read() + .width() + .variant() + .unwrap() + .into(); + + let count = p + .channel(self.channel.number().into()) + .xfercfg() + .read() + .xfercount() + .bits() + + 1; + + usize::from(count) * usize::from(width) + } +} + +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + self.abort(); + } +} + +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Re-register the waker on each call to poll() because any calls to + // wake will deregister the waker. + CHANNEL_WAKERS[self.channel.number() as usize].register(cx.waker()); + + if self.channel.regs().active0().read().act().bits() & (1 << self.channel.number()) == 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +/// DMA channel descriptor +#[derive(Copy, Clone)] +#[repr(C)] +struct Descriptor { + reserved: u32, + src: u32, + dest: u32, + link: u32, +} + +impl Descriptor { + const fn new() -> Self { + Self { + reserved: 0, + src: 0, + dest: 0, + link: 0, + } + } +} + +#[repr(align(1024))] +struct Descriptors { + descs: [Descriptor; CHANNEL_COUNT], +} + +impl Descriptors { + const fn new() -> Self { + Self { + descs: [const { Descriptor::new() }; CHANNEL_COUNT], + } + } +} + +static mut DESCRIPTORS: Descriptors = Descriptors::new(); +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; +pub(crate) const CHANNEL_COUNT: usize = 33; + +/// DMA channel interface. +#[allow(private_bounds)] +pub trait Channel: PeripheralType + Sealed + Into + Sized + 'static { + /// Channel number. + fn number(&self) -> u8; + + /// Channel registry block. + fn regs(&self) -> &'static pac::dma0::RegisterBlock { + unsafe { &*crate::pac::Dma0::ptr() } + } +} + +/// DMA word. +#[allow(private_bounds)] +pub trait Word: Sealed { + /// Transfer width. + fn width() -> Width; + + /// Size in bytes for the width. + fn size() -> isize; +} + +impl Sealed for u8 {} +impl Word for u8 { + fn width() -> Width { + Width::Bit8 + } + + fn size() -> isize { + 1 + } +} + +impl Sealed for u16 {} +impl Word for u16 { + fn width() -> Width { + Width::Bit16 + } + + fn size() -> isize { + 2 + } +} + +impl Sealed for u32 {} +impl Word for u32 { + fn width() -> Width { + Width::Bit32 + } + + fn size() -> isize { + 4 + } +} + +/// Type erased DMA channel. +pub struct AnyChannel { + number: u8, +} + +impl_peripheral!(AnyChannel); + +impl Sealed for AnyChannel {} +impl Channel for AnyChannel { + fn number(&self) -> u8 { + self.number + } +} + +macro_rules! channel { + ($name:ident, $num:expr) => { + impl Sealed for peripherals::$name {} + impl Channel for peripherals::$name { + fn number(&self) -> u8 { + $num + } + } + + impl From for crate::dma::AnyChannel { + fn from(val: peripherals::$name) -> Self { + Self { number: val.number() } + } + } + }; +} + +channel!(DMA0_CH0, 0); +channel!(DMA0_CH1, 1); +channel!(DMA0_CH2, 2); +channel!(DMA0_CH3, 3); +channel!(DMA0_CH4, 4); +channel!(DMA0_CH5, 5); +channel!(DMA0_CH6, 6); +channel!(DMA0_CH7, 7); +channel!(DMA0_CH8, 8); +channel!(DMA0_CH9, 9); +channel!(DMA0_CH10, 10); +channel!(DMA0_CH11, 11); +channel!(DMA0_CH12, 12); +channel!(DMA0_CH13, 13); +channel!(DMA0_CH14, 14); +channel!(DMA0_CH15, 15); +channel!(DMA0_CH16, 16); +channel!(DMA0_CH17, 17); +channel!(DMA0_CH18, 18); +channel!(DMA0_CH19, 19); +channel!(DMA0_CH20, 20); +channel!(DMA0_CH21, 21); +channel!(DMA0_CH22, 22); +channel!(DMA0_CH23, 23); +channel!(DMA0_CH24, 24); +channel!(DMA0_CH25, 25); +channel!(DMA0_CH26, 26); +channel!(DMA0_CH27, 27); +channel!(DMA0_CH28, 28); +channel!(DMA0_CH29, 29); +channel!(DMA0_CH30, 30); +channel!(DMA0_CH31, 31); +channel!(DMA0_CH32, 32); diff --git a/embassy-imxrt/src/flexcomm/mod.rs b/embassy-imxrt/src/flexcomm/mod.rs new file mode 100644 index 000000000..4473c9a77 --- /dev/null +++ b/embassy-imxrt/src/flexcomm/mod.rs @@ -0,0 +1,252 @@ +//! Implements Flexcomm interface wrapper for easier usage across modules + +pub mod uart; + +use paste::paste; + +use crate::clocks::{enable_and_reset, SysconPeripheral}; +use crate::peripherals::{ + FLEXCOMM0, FLEXCOMM1, FLEXCOMM14, FLEXCOMM15, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, +}; +use crate::{pac, PeripheralType}; + +/// clock selection option +#[derive(Copy, Clone, Debug)] +pub enum Clock { + /// SFRO + Sfro, + + /// FFRO + Ffro, + + /// `AUDIO_PLL` + AudioPll, + + /// MASTER + Master, + + /// FCn_FRG with Main clock source + FcnFrgMain, + + /// FCn_FRG with Pll clock source + FcnFrgPll, + + /// FCn_FRG with Sfro clock source + FcnFrgSfro, + + /// FCn_FRG with Ffro clock source + FcnFrgFfro, + + /// disabled + None, +} + +/// do not allow implementation of trait outside this mod +mod sealed { + /// trait does not get re-exported outside flexcomm mod, allowing us to safely expose only desired APIs + pub trait Sealed {} +} + +/// primary low-level flexcomm interface +pub(crate) trait FlexcommLowLevel: sealed::Sealed + PeripheralType + SysconPeripheral + 'static + Send { + // fetch the flexcomm register block for direct manipulation + fn reg() -> &'static pac::flexcomm0::RegisterBlock; + + // set the clock select for this flexcomm instance and remove from reset + fn enable(clk: Clock); +} + +macro_rules! impl_flexcomm { + ($($idx:expr),*) => { + $( + paste!{ + impl sealed::Sealed for crate::peripherals::[] {} + + impl FlexcommLowLevel for crate::peripherals::[] { + fn reg() -> &'static crate::pac::flexcomm0::RegisterBlock { + // SAFETY: safe from single executor, enforce + // via peripheral reference lifetime tracking + unsafe { + &*crate::pac::[]::ptr() + } + } + + fn enable(clk: Clock) { + // SAFETY: safe from single executor + let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; + + clkctl1.flexcomm($idx).fcfclksel().write(|w| match clk { + Clock::Sfro => w.sel().sfro_clk(), + Clock::Ffro => w.sel().ffro_clk(), + Clock::AudioPll => w.sel().audio_pll_clk(), + Clock::Master => w.sel().master_clk(), + Clock::FcnFrgMain => w.sel().fcn_frg_clk(), + Clock::FcnFrgPll => w.sel().fcn_frg_clk(), + Clock::FcnFrgSfro => w.sel().fcn_frg_clk(), + Clock::FcnFrgFfro => w.sel().fcn_frg_clk(), + Clock::None => w.sel().none(), // no clock? throw an error? + }); + + clkctl1.flexcomm($idx).frgclksel().write(|w| match clk { + Clock::FcnFrgMain => w.sel().main_clk(), + Clock::FcnFrgPll => w.sel().frg_pll_clk(), + Clock::FcnFrgSfro => w.sel().sfro_clk(), + Clock::FcnFrgFfro => w.sel().ffro_clk(), + _ => w.sel().none(), // not using frg ... + }); + + // todo: add support for frg div/mult + clkctl1 + .flexcomm($idx) + .frgctl() + .write(|w| + // SAFETY: unsafe only used for .bits() call + unsafe { w.mult().bits(0) }); + + enable_and_reset::<[]>(); + } + } + } + )* + } +} + +impl_flexcomm!(0, 1, 2, 3, 4, 5, 6, 7); + +// TODO: FLEXCOMM 14 is untested. Enable SPI support on FLEXCOMM14 +// Add special case FLEXCOMM14 +impl sealed::Sealed for crate::peripherals::FLEXCOMM14 {} + +impl FlexcommLowLevel for crate::peripherals::FLEXCOMM14 { + fn reg() -> &'static crate::pac::flexcomm0::RegisterBlock { + // SAFETY: safe from single executor, enforce + // via peripheral reference lifetime tracking + unsafe { &*crate::pac::Flexcomm14::ptr() } + } + + fn enable(clk: Clock) { + // SAFETY: safe from single executor + let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; + + clkctl1.fc14fclksel().write(|w| match clk { + Clock::Sfro => w.sel().sfro_clk(), + Clock::Ffro => w.sel().ffro_clk(), + Clock::AudioPll => w.sel().audio_pll_clk(), + Clock::Master => w.sel().master_clk(), + Clock::FcnFrgMain => w.sel().fcn_frg_clk(), + Clock::FcnFrgPll => w.sel().fcn_frg_clk(), + Clock::FcnFrgSfro => w.sel().fcn_frg_clk(), + Clock::FcnFrgFfro => w.sel().fcn_frg_clk(), + Clock::None => w.sel().none(), // no clock? throw an error? + }); + + clkctl1.frg14clksel().write(|w| match clk { + Clock::FcnFrgMain => w.sel().main_clk(), + Clock::FcnFrgPll => w.sel().frg_pll_clk(), + Clock::FcnFrgSfro => w.sel().sfro_clk(), + Clock::FcnFrgFfro => w.sel().ffro_clk(), + _ => w.sel().none(), // not using frg ... + }); + + // todo: add support for frg div/mult + clkctl1.frg14ctl().write(|w| + // SAFETY: unsafe only used for .bits() call + unsafe { w.mult().bits(0) }); + + enable_and_reset::(); + } +} + +// Add special case FLEXCOMM15 +impl sealed::Sealed for crate::peripherals::FLEXCOMM15 {} + +impl FlexcommLowLevel for crate::peripherals::FLEXCOMM15 { + fn reg() -> &'static crate::pac::flexcomm0::RegisterBlock { + // SAFETY: safe from single executor, enforce + // via peripheral reference lifetime tracking + unsafe { &*crate::pac::Flexcomm15::ptr() } + } + + fn enable(clk: Clock) { + // SAFETY: safe from single executor + let clkctl1 = unsafe { crate::pac::Clkctl1::steal() }; + + clkctl1.fc15fclksel().write(|w| match clk { + Clock::Sfro => w.sel().sfro_clk(), + Clock::Ffro => w.sel().ffro_clk(), + Clock::AudioPll => w.sel().audio_pll_clk(), + Clock::Master => w.sel().master_clk(), + Clock::FcnFrgMain => w.sel().fcn_frg_clk(), + Clock::FcnFrgPll => w.sel().fcn_frg_clk(), + Clock::FcnFrgSfro => w.sel().fcn_frg_clk(), + Clock::FcnFrgFfro => w.sel().fcn_frg_clk(), + Clock::None => w.sel().none(), // no clock? throw an error? + }); + clkctl1.frg15clksel().write(|w| match clk { + Clock::FcnFrgMain => w.sel().main_clk(), + Clock::FcnFrgPll => w.sel().frg_pll_clk(), + Clock::FcnFrgSfro => w.sel().sfro_clk(), + Clock::FcnFrgFfro => w.sel().ffro_clk(), + _ => w.sel().none(), // not using frg ... + }); + // todo: add support for frg div/mult + clkctl1.frg15ctl().write(|w| + // SAFETY: unsafe only used for .bits() call + unsafe { w.mult().bits(0) }); + + enable_and_reset::(); + } +} + +macro_rules! into_mode { + ($mode:ident, $($fc:ident),*) => { + paste! { + /// Sealed Mode trait + trait []: FlexcommLowLevel {} + + /// Select mode of operation + #[allow(private_bounds)] + pub trait []: [] { + /// Set mode of operation + fn []() { + Self::reg().pselid().write(|w| w.persel().[<$mode>]()); + } + } + } + + $( + paste!{ + impl [] for crate::peripherals::$fc {} + impl [] for crate::peripherals::$fc {} + } + )* + } +} + +into_mode!(usart, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7); +into_mode!(spi, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXCOMM14); +into_mode!(i2c, FLEXCOMM0, FLEXCOMM1, FLEXCOMM2, FLEXCOMM3, FLEXCOMM4, FLEXCOMM5, FLEXCOMM6, FLEXCOMM7, FLEXCOMM15); + +into_mode!( + i2s_transmit, + FLEXCOMM0, + FLEXCOMM1, + FLEXCOMM2, + FLEXCOMM3, + FLEXCOMM4, + FLEXCOMM5, + FLEXCOMM6, + FLEXCOMM7 +); + +into_mode!( + i2s_receive, + FLEXCOMM0, + FLEXCOMM1, + FLEXCOMM2, + FLEXCOMM3, + FLEXCOMM4, + FLEXCOMM5, + FLEXCOMM6, + FLEXCOMM7 +); diff --git a/embassy-imxrt/src/flexcomm/uart.rs b/embassy-imxrt/src/flexcomm/uart.rs new file mode 100644 index 000000000..230b30d43 --- /dev/null +++ b/embassy-imxrt/src/flexcomm/uart.rs @@ -0,0 +1,1230 @@ +//! Universal Asynchronous Receiver Transmitter (UART) driver. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, AtomicU8, Ordering}; +use core::task::Poll; + +use embassy_futures::select::{select, Either}; +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use paste::paste; + +use crate::dma::AnyChannel; +use crate::flexcomm::Clock; +use crate::gpio::{AnyPin, GpioPin as Pin}; +use crate::interrupt::typelevel::Interrupt; +use crate::iopctl::{DriveMode, DriveStrength, Inverter, IopctlPin, Pull, SlewRate}; +use crate::pac::usart0::cfg::{Clkpol, Datalen, Loop, Paritysel as Parity, Stoplen, Syncen, Syncmst}; +use crate::pac::usart0::ctl::Cc; +use crate::sealed::Sealed; +use crate::{dma, interrupt}; + +/// Driver move trait. +#[allow(private_bounds)] +pub trait Mode: Sealed {} + +/// Blocking mode. +pub struct Blocking; +impl Sealed for Blocking {} +impl Mode for Blocking {} + +/// Async mode. +pub struct Async; +impl Sealed for Async {} +impl Mode for Async {} + +/// Uart driver. +pub struct Uart<'a, M: Mode> { + tx: UartTx<'a, M>, + rx: UartRx<'a, M>, +} + +/// Uart TX driver. +pub struct UartTx<'a, M: Mode> { + info: Info, + tx_dma: Option>, + _phantom: PhantomData<(&'a (), M)>, +} + +/// Uart RX driver. +pub struct UartRx<'a, M: Mode> { + info: Info, + rx_dma: Option>, + _phantom: PhantomData<(&'a (), M)>, +} + +/// UART config +#[derive(Clone, Copy)] +pub struct Config { + /// Baudrate of the Uart + pub baudrate: u32, + /// data length + pub data_bits: Datalen, + /// Parity + pub parity: Parity, + /// Stop bits + pub stop_bits: Stoplen, + /// Polarity of the clock + pub clock_polarity: Clkpol, + /// Sync/ Async operation selection + pub operation: Syncen, + /// Sync master/slave mode selection (only applicable in sync mode) + pub sync_mode_master_select: Syncmst, + /// USART continuous Clock generation enable in synchronous master mode. + pub continuous_clock: Cc, + /// Normal/ loopback mode + pub loopback_mode: Loop, + /// Clock type + pub clock: Clock, +} + +impl Default for Config { + /// Default configuration for single channel sampling. + fn default() -> Self { + Self { + baudrate: 115_200, + data_bits: Datalen::Bit8, + parity: Parity::NoParity, + stop_bits: Stoplen::Bit1, + clock_polarity: Clkpol::FallingEdge, + operation: Syncen::AsynchronousMode, + sync_mode_master_select: Syncmst::Slave, + continuous_clock: Cc::ClockOnCharacter, + loopback_mode: Loop::Normal, + clock: crate::flexcomm::Clock::Sfro, + } + } +} + +/// Uart Errors +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Read error + Read, + + /// Buffer overflow + Overrun, + + /// Noise error + Noise, + + /// Framing error + Framing, + + /// Parity error + Parity, + + /// Failure + Fail, + + /// Invalid argument + InvalidArgument, + + /// Uart baud rate cannot be supported with the given clock + UnsupportedBaudrate, + + /// RX FIFO Empty + RxFifoEmpty, + + /// TX FIFO Full + TxFifoFull, + + /// TX Busy + TxBusy, +} +/// shorthand for -> `Result` +pub type Result = core::result::Result; + +impl<'a, M: Mode> UartTx<'a, M> { + fn new_inner(tx_dma: Option>) -> Self { + let uarttx = Self { + info: T::info(), + tx_dma, + _phantom: PhantomData, + }; + uarttx.info.refcnt.fetch_add(1, Ordering::Relaxed); + uarttx + } +} + +impl<'a, M: Mode> Drop for UartTx<'a, M> { + fn drop(&mut self) { + if self.info.refcnt.fetch_sub(1, Ordering::Relaxed) == 1 { + while self.info.regs.stat().read().txidle().bit_is_clear() {} + + self.info.regs.fifointenclr().modify(|_, w| { + w.txerr() + .set_bit() + .rxerr() + .set_bit() + .txlvl() + .set_bit() + .rxlvl() + .set_bit() + }); + + self.info + .regs + .fifocfg() + .modify(|_, w| w.dmatx().clear_bit().dmarx().clear_bit()); + + self.info.regs.cfg().modify(|_, w| w.enable().disabled()); + } + } +} + +impl<'a> UartTx<'a, Blocking> { + /// Create a new UART which can only send data + /// Unidirectional Uart - Tx only + pub fn new_blocking(_inner: Peri<'a, T>, tx: Peri<'a, impl TxPin>, config: Config) -> Result { + tx.as_tx(); + + let _tx = tx.into(); + Uart::::init::(Some(_tx), None, None, None, config)?; + + Ok(Self::new_inner::(None)) + } + + fn write_byte_internal(&mut self, byte: u8) -> Result<()> { + // SAFETY: unsafe only used for .bits() + self.info + .regs + .fifowr() + .write(|w| unsafe { w.txdata().bits(u16::from(byte)) }); + + Ok(()) + } + + fn blocking_write_byte(&mut self, byte: u8) -> Result<()> { + while self.info.regs.fifostat().read().txnotfull().bit_is_clear() {} + + // Prevent the compiler from reordering write_byte_internal() + // before the loop above. + compiler_fence(Ordering::Release); + + self.write_byte_internal(byte) + } + + fn write_byte(&mut self, byte: u8) -> Result<()> { + if self.info.regs.fifostat().read().txnotfull().bit_is_clear() { + Err(Error::TxFifoFull) + } else { + self.write_byte_internal(byte) + } + } + + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { + for x in buf { + self.blocking_write_byte(*x)?; + } + + Ok(()) + } + + /// Transmit the provided buffer. Non-blocking version, bails out + /// if it would block. + pub fn write(&mut self, buf: &[u8]) -> Result<()> { + for x in buf { + self.write_byte(*x)?; + } + + Ok(()) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<()> { + while self.info.regs.stat().read().txidle().bit_is_clear() {} + Ok(()) + } + + /// Flush UART TX. + pub fn flush(&mut self) -> Result<()> { + if self.info.regs.stat().read().txidle().bit_is_clear() { + Err(Error::TxBusy) + } else { + Ok(()) + } + } +} + +impl<'a, M: Mode> UartRx<'a, M> { + fn new_inner(rx_dma: Option>) -> Self { + let uartrx = Self { + info: T::info(), + rx_dma, + _phantom: PhantomData, + }; + uartrx.info.refcnt.fetch_add(1, Ordering::Relaxed); + uartrx + } +} + +impl<'a, M: Mode> Drop for UartRx<'a, M> { + fn drop(&mut self) { + if self.info.refcnt.fetch_sub(1, Ordering::Relaxed) == 1 { + while self.info.regs.stat().read().rxidle().bit_is_clear() {} + + self.info.regs.fifointenclr().modify(|_, w| { + w.txerr() + .set_bit() + .rxerr() + .set_bit() + .txlvl() + .set_bit() + .rxlvl() + .set_bit() + }); + + self.info + .regs + .fifocfg() + .modify(|_, w| w.dmatx().clear_bit().dmarx().clear_bit()); + + self.info.regs.cfg().modify(|_, w| w.enable().disabled()); + } + } +} + +impl<'a> UartRx<'a, Blocking> { + /// Create a new blocking UART which can only receive data + pub fn new_blocking(_inner: Peri<'a, T>, rx: Peri<'a, impl RxPin>, config: Config) -> Result { + rx.as_rx(); + + let _rx = rx.into(); + Uart::::init::(None, Some(_rx), None, None, config)?; + + Ok(Self::new_inner::(None)) + } +} + +impl UartRx<'_, Blocking> { + fn read_byte_internal(&mut self) -> Result { + if self.info.regs.fifostat().read().rxerr().bit_is_set() { + self.info.regs.fifocfg().modify(|_, w| w.emptyrx().set_bit()); + self.info.regs.fifostat().modify(|_, w| w.rxerr().set_bit()); + Err(Error::Read) + } else if self.info.regs.stat().read().parityerrint().bit_is_set() { + self.info.regs.stat().modify(|_, w| w.parityerrint().clear_bit_by_one()); + Err(Error::Parity) + } else if self.info.regs.stat().read().framerrint().bit_is_set() { + self.info.regs.stat().modify(|_, w| w.framerrint().clear_bit_by_one()); + Err(Error::Framing) + } else if self.info.regs.stat().read().rxnoiseint().bit_is_set() { + self.info.regs.stat().modify(|_, w| w.rxnoiseint().clear_bit_by_one()); + Err(Error::Noise) + } else { + let byte = self.info.regs.fiford().read().rxdata().bits() as u8; + Ok(byte) + } + } + + fn read_byte(&mut self) -> Result { + if self.info.regs.fifostat().read().rxnotempty().bit_is_clear() { + Err(Error::RxFifoEmpty) + } else { + self.read_byte_internal() + } + } + + fn blocking_read_byte(&mut self) -> Result { + while self.info.regs.fifostat().read().rxnotempty().bit_is_clear() {} + + // Prevent the compiler from reordering read_byte_internal() + // before the loop above. + compiler_fence(Ordering::Acquire); + + self.read_byte_internal() + } + + /// Read from UART RX. Non-blocking version, bails out if it would + /// block. + pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { + for b in buf.iter_mut() { + *b = self.read_byte()?; + } + + Ok(()) + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { + for b in buf.iter_mut() { + *b = self.blocking_read_byte()?; + } + + Ok(()) + } +} + +impl<'a, M: Mode> Uart<'a, M> { + fn init( + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) -> Result<()> { + T::enable(config.clock); + T::into_usart(); + + let regs = T::info().regs; + + if tx.is_some() { + regs.fifocfg().modify(|_, w| w.emptytx().set_bit().enabletx().enabled()); + + // clear FIFO error + regs.fifostat().write(|w| w.txerr().set_bit()); + } + + if rx.is_some() { + regs.fifocfg().modify(|_, w| w.emptyrx().set_bit().enablerx().enabled()); + + // clear FIFO error + regs.fifostat().write(|w| w.rxerr().set_bit()); + } + + if rts.is_some() && cts.is_some() { + regs.cfg().modify(|_, w| w.ctsen().enabled()); + } + + Self::set_baudrate_inner::(config.baudrate, config.clock)?; + Self::set_uart_config::(config); + + Ok(()) + } + + fn get_fc_freq(clock: Clock) -> Result { + match clock { + Clock::Sfro => Ok(16_000_000), + Clock::Ffro => Ok(48_000_000), + // We only support Sfro and Ffro now. + _ => Err(Error::InvalidArgument), + } + } + + fn set_baudrate_inner(baudrate: u32, clock: Clock) -> Result<()> { + // Get source clock frequency according to clock type. + let source_clock_hz = Self::get_fc_freq(clock)?; + + if baudrate == 0 { + return Err(Error::InvalidArgument); + } + + let regs = T::info().regs; + + // If synchronous master mode is enabled, only configure the BRG value. + if regs.cfg().read().syncen().is_synchronous_mode() { + // Master + if regs.cfg().read().syncmst().is_master() { + // Calculate the BRG value + let brgval = (source_clock_hz / baudrate) - 1; + + // SAFETY: unsafe only used for .bits() + regs.brg().write(|w| unsafe { w.brgval().bits(brgval as u16) }); + } + } else { + // Smaller values of OSR can make the sampling position within a + // data bit less accurate and may potentially cause more noise + // errors or incorrect data. + let (_, osr, brg) = (8..16).rev().fold( + (u32::MAX, u32::MAX, u32::MAX), + |(best_diff, best_osr, best_brg), osrval| { + // Compare source_clock_hz agaist with ((osrval + 1) * baudrate) to make sure + // (source_clock_hz / ((osrval + 1) * baudrate)) is not less than 0. + if source_clock_hz < ((osrval + 1) * baudrate) { + (best_diff, best_osr, best_brg) + } else { + let brgval = (source_clock_hz / ((osrval + 1) * baudrate)) - 1; + // We know brgval will not be less than 0 now, it should have already been a valid u32 value, + // then compare it agaist with 65535. + if brgval > 65535 { + (best_diff, best_osr, best_brg) + } else { + // Calculate the baud rate based on the BRG value + let candidate = source_clock_hz / ((osrval + 1) * (brgval + 1)); + + // Calculate the difference between the + // current baud rate and the desired baud rate + let diff = (candidate as i32 - baudrate as i32).unsigned_abs(); + + // Check if the current calculated difference is the best so far + if diff < best_diff { + (diff, osrval, brgval) + } else { + (best_diff, best_osr, best_brg) + } + } + } + }, + ); + + // Value over range + if brg > 65535 { + return Err(Error::UnsupportedBaudrate); + } + + // SAFETY: unsafe only used for .bits() + regs.osr().write(|w| unsafe { w.osrval().bits(osr as u8) }); + + // SAFETY: unsafe only used for .bits() + regs.brg().write(|w| unsafe { w.brgval().bits(brg as u16) }); + } + + Ok(()) + } + + fn set_uart_config(config: Config) { + let regs = T::info().regs; + + regs.cfg().modify(|_, w| w.enable().disabled()); + + regs.cfg().modify(|_, w| { + w.datalen() + .variant(config.data_bits) + .stoplen() + .variant(config.stop_bits) + .paritysel() + .variant(config.parity) + .loop_() + .variant(config.loopback_mode) + .syncen() + .variant(config.operation) + .clkpol() + .variant(config.clock_polarity) + }); + + regs.cfg().modify(|_, w| w.enable().enabled()); + } + + /// Split the Uart into a transmitter and receiver, which is particularly + /// useful when having two tasks correlating to transmitting and receiving. + pub fn split(self) -> (UartTx<'a, M>, UartRx<'a, M>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut UartTx<'a, M>, &mut UartRx<'a, M>) { + (&mut self.tx, &mut self.rx) + } +} + +impl<'a> Uart<'a, Blocking> { + /// Create a new blocking UART + pub fn new_blocking( + _inner: Peri<'a, T>, + tx: Peri<'a, impl TxPin>, + rx: Peri<'a, impl RxPin>, + config: Config, + ) -> Result { + tx.as_tx(); + rx.as_rx(); + + let tx = tx.into(); + let rx = rx.into(); + + Self::init::(Some(tx), Some(rx), None, None, config)?; + + Ok(Self { + tx: UartTx::new_inner::(None), + rx: UartRx::new_inner::(None), + }) + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result<()> { + self.rx.blocking_read(buf) + } + + /// Read from UART RX. Non-blocking version, bails out if it would + /// block. + pub fn read(&mut self, buf: &mut [u8]) -> Result<()> { + self.rx.read(buf) + } + + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buf: &[u8]) -> Result<()> { + self.tx.blocking_write(buf) + } + + /// Transmit the provided buffer. Non-blocking version, bails out + /// if it would block. + pub fn write(&mut self, buf: &[u8]) -> Result<()> { + self.tx.write(buf) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<()> { + self.tx.blocking_flush() + } + + /// Flush UART TX. + pub fn flush(&mut self) -> Result<()> { + self.tx.flush() + } +} + +impl<'a> UartTx<'a, Async> { + /// Create a new DMA enabled UART which can only send data + pub fn new_async( + _inner: Peri<'a, T>, + tx: Peri<'a, impl TxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_dma: Peri<'a, impl TxDma>, + config: Config, + ) -> Result { + tx.as_tx(); + + let _tx = tx.into(); + Uart::::init::(Some(_tx), None, None, None, config)?; + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Ok(Self::new_inner::(Some(tx_dma.into()))) + } + + /// Transmit the provided buffer asynchronously. + pub async fn write(&mut self, buf: &[u8]) -> Result<()> { + let regs = self.info.regs; + + // Disable DMA on completion/cancellation + let _dma_guard = OnDrop::new(|| { + regs.fifocfg().modify(|_, w| w.dmatx().disabled()); + }); + + for chunk in buf.chunks(1024) { + regs.fifocfg().modify(|_, w| w.dmatx().enabled()); + + let ch = self.tx_dma.as_mut().unwrap().reborrow(); + let transfer = unsafe { dma::write(ch, chunk, regs.fifowr().as_ptr() as *mut u8) }; + + let res = select( + transfer, + poll_fn(|cx| { + UART_WAKERS[self.info.index].register(cx.waker()); + + self.info.regs.intenset().write(|w| { + w.framerren() + .set_bit() + .parityerren() + .set_bit() + .rxnoiseen() + .set_bit() + .aberren() + .set_bit() + }); + + let stat = self.info.regs.stat().read(); + + self.info.regs.stat().write(|w| { + w.framerrint() + .clear_bit_by_one() + .parityerrint() + .clear_bit_by_one() + .rxnoiseint() + .clear_bit_by_one() + .aberr() + .clear_bit_by_one() + }); + + if stat.framerrint().bit_is_set() { + Poll::Ready(Err(Error::Framing)) + } else if stat.parityerrint().bit_is_set() { + Poll::Ready(Err(Error::Parity)) + } else if stat.rxnoiseint().bit_is_set() { + Poll::Ready(Err(Error::Noise)) + } else if stat.aberr().bit_is_set() { + Poll::Ready(Err(Error::Fail)) + } else { + Poll::Pending + } + }), + ) + .await; + + match res { + Either::First(()) | Either::Second(Ok(())) => (), + Either::Second(e) => return e, + } + } + + Ok(()) + } + + /// Flush UART TX asynchronously. + pub async fn flush(&mut self) -> Result<()> { + self.wait_on( + |me| { + if me.info.regs.stat().read().txidle().bit_is_set() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }, + |me| { + me.info.regs.intenset().write(|w| w.txidleen().set_bit()); + }, + ) + .await + } + + /// Calls `f` to check if we are ready or not. + /// If not, `g` is called once the waker is set (to eg enable the required interrupts). + async fn wait_on(&mut self, mut f: F, mut g: G) -> U + where + F: FnMut(&mut Self) -> Poll, + G: FnMut(&mut Self), + { + poll_fn(|cx| { + // Register waker before checking condition, to ensure that wakes/interrupts + // aren't lost between f() and g() + UART_WAKERS[self.info.index].register(cx.waker()); + let r = f(self); + + if r.is_pending() { + g(self); + } + + r + }) + .await + } +} + +impl<'a> UartRx<'a, Async> { + /// Create a new DMA enabled UART which can only receive data + pub fn new_async( + _inner: Peri<'a, T>, + rx: Peri<'a, impl RxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + rx_dma: Peri<'a, impl RxDma>, + config: Config, + ) -> Result { + rx.as_rx(); + + let _rx = rx.into(); + Uart::::init::(None, Some(_rx), None, None, config)?; + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Ok(Self::new_inner::(Some(rx_dma.into()))) + } + + /// Read from UART RX asynchronously. + pub async fn read(&mut self, buf: &mut [u8]) -> Result<()> { + let regs = self.info.regs; + + // Disable DMA on completion/cancellation + let _dma_guard = OnDrop::new(|| { + regs.fifocfg().modify(|_, w| w.dmarx().disabled()); + }); + + for chunk in buf.chunks_mut(1024) { + regs.fifocfg().modify(|_, w| w.dmarx().enabled()); + + let ch = self.rx_dma.as_mut().unwrap().reborrow(); + let transfer = unsafe { dma::read(ch, regs.fiford().as_ptr() as *const u8, chunk) }; + + let res = select( + transfer, + poll_fn(|cx| { + UART_WAKERS[self.info.index].register(cx.waker()); + + self.info.regs.intenset().write(|w| { + w.framerren() + .set_bit() + .parityerren() + .set_bit() + .rxnoiseen() + .set_bit() + .aberren() + .set_bit() + }); + + let stat = self.info.regs.stat().read(); + + self.info.regs.stat().write(|w| { + w.framerrint() + .clear_bit_by_one() + .parityerrint() + .clear_bit_by_one() + .rxnoiseint() + .clear_bit_by_one() + .aberr() + .clear_bit_by_one() + }); + + if stat.framerrint().bit_is_set() { + Poll::Ready(Err(Error::Framing)) + } else if stat.parityerrint().bit_is_set() { + Poll::Ready(Err(Error::Parity)) + } else if stat.rxnoiseint().bit_is_set() { + Poll::Ready(Err(Error::Noise)) + } else if stat.aberr().bit_is_set() { + Poll::Ready(Err(Error::Fail)) + } else { + Poll::Pending + } + }), + ) + .await; + + match res { + Either::First(()) | Either::Second(Ok(())) => (), + Either::Second(e) => return e, + } + } + + Ok(()) + } +} + +impl<'a> Uart<'a, Async> { + /// Create a new DMA enabled UART + pub fn new_async( + _inner: Peri<'a, T>, + tx: Peri<'a, impl TxPin>, + rx: Peri<'a, impl RxPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_dma: Peri<'a, impl TxDma>, + rx_dma: Peri<'a, impl RxDma>, + config: Config, + ) -> Result { + tx.as_tx(); + rx.as_rx(); + + let tx = tx.into(); + let rx = rx.into(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self::init::(Some(tx), Some(rx), None, None, config)?; + + Ok(Self { + tx: UartTx::new_inner::(Some(tx_dma.into())), + rx: UartRx::new_inner::(Some(rx_dma.into())), + }) + } + + /// Create a new DMA enabled UART with hardware flow control (RTS/CTS) + pub fn new_with_rtscts( + _inner: Peri<'a, T>, + tx: Peri<'a, impl TxPin>, + rx: Peri<'a, impl RxPin>, + rts: Peri<'a, impl RtsPin>, + cts: Peri<'a, impl CtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'a, + tx_dma: Peri<'a, impl TxDma>, + rx_dma: Peri<'a, impl RxDma>, + config: Config, + ) -> Result { + tx.as_tx(); + rx.as_rx(); + rts.as_rts(); + cts.as_cts(); + + let tx = tx.into(); + let rx = rx.into(); + let rts = rts.into(); + let cts = cts.into(); + + Self::init::(Some(tx), Some(rx), Some(rts), Some(cts), config)?; + + Ok(Self { + tx: UartTx::new_inner::(Some(tx_dma.into())), + rx: UartRx::new_inner::(Some(rx_dma.into())), + }) + } + + /// Read from UART RX. + pub async fn read(&mut self, buf: &mut [u8]) -> Result<()> { + self.rx.read(buf).await + } + + /// Transmit the provided buffer. + pub async fn write(&mut self, buf: &[u8]) -> Result<()> { + self.tx.write(buf).await + } + + /// Flush UART TX. + pub async fn flush(&mut self) -> Result<()> { + self.tx.flush().await + } +} + +impl embedded_hal_02::serial::Read for UartRx<'_, Blocking> { + type Error = Error; + + fn read(&mut self) -> core::result::Result> { + let mut buf = [0; 1]; + + match self.read(&mut buf) { + Ok(_) => Ok(buf[0]), + Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_02::serial::Write for UartTx<'_, Blocking> { + type Error = Error; + + fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { + match self.write(&[word]) { + Ok(_) => Ok(()), + Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } + + fn flush(&mut self) -> core::result::Result<(), nb::Error> { + match self.flush() { + Ok(_) => Ok(()), + Err(Error::TxBusy) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_02::blocking::serial::Write for UartTx<'_, Blocking> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_02::serial::Read for Uart<'_, Blocking> { + type Error = Error; + + fn read(&mut self) -> core::result::Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_02::serial::Write for Uart<'_, Blocking> { + type Error = Error; + + fn write(&mut self, word: u8) -> core::result::Result<(), nb::Error> { + embedded_hal_02::serial::Write::write(&mut self.tx, word) + } + + fn flush(&mut self) -> core::result::Result<(), nb::Error> { + embedded_hal_02::serial::Write::flush(&mut self.tx) + } +} + +impl embedded_hal_02::blocking::serial::Write for Uart<'_, Blocking> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> core::result::Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> core::result::Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + Self::Noise => embedded_hal_nb::serial::ErrorKind::Noise, + _ => embedded_hal_nb::serial::ErrorKind::Other, + } + } +} + +impl embedded_hal_nb::serial::ErrorType for UartRx<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for UartTx<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for Uart<'_, Blocking> { + type Error = Error; +} + +impl embedded_hal_nb::serial::Read for UartRx<'_, Blocking> { + fn read(&mut self) -> nb::Result { + let mut buf = [0; 1]; + + match self.read(&mut buf) { + Ok(_) => Ok(buf[0]), + Err(Error::RxFifoEmpty) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_nb::serial::Write for UartTx<'_, Blocking> { + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + match self.write(&[word]) { + Ok(_) => Ok(()), + Err(Error::TxFifoFull) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + match self.flush() { + Ok(_) => Ok(()), + Err(Error::TxBusy) => Err(nb::Error::WouldBlock), + Err(e) => Err(nb::Error::Other(e)), + } + } +} + +impl embedded_hal_nb::serial::Read for Uart<'_, Blocking> { + fn read(&mut self) -> core::result::Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_nb::serial::Write for Uart<'_, Blocking> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +struct Info { + regs: &'static crate::pac::usart0::RegisterBlock, + index: usize, + refcnt: AtomicU8, +} + +trait SealedInstance { + fn info() -> Info; + fn index() -> usize; +} + +/// UART interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +const UART_COUNT: usize = 8; +static UART_WAKERS: [AtomicWaker; UART_COUNT] = [const { AtomicWaker::new() }; UART_COUNT]; + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let waker = &UART_WAKERS[T::index()]; + let regs = T::info().regs; + let stat = regs.intstat().read(); + + if stat.txidle().bit_is_set() + || stat.framerrint().bit_is_set() + || stat.parityerrint().bit_is_set() + || stat.rxnoiseint().bit_is_set() + || stat.aberrint().bit_is_set() + { + regs.intenclr().write(|w| { + w.txidleclr() + .set_bit() + .framerrclr() + .set_bit() + .parityerrclr() + .set_bit() + .rxnoiseclr() + .set_bit() + .aberrclr() + .set_bit() + }); + } + + waker.wake(); + } +} + +/// UART instance trait. +#[allow(private_bounds)] +pub trait Instance: crate::flexcomm::IntoUsart + SealedInstance + PeripheralType + 'static + Send { + /// Interrupt for this UART instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($($n:expr),*) => { + $( + paste!{ + impl SealedInstance for crate::peripherals::[] { + fn info() -> Info { + Info { + regs: unsafe { &*crate::pac::[]::ptr() }, + index: $n, + refcnt: AtomicU8::new(0), + } + } + + #[inline] + fn index() -> usize { + $n + } + } + + impl Instance for crate::peripherals::[] { + type Interrupt = crate::interrupt::typelevel::[]; + } + } + )* + }; +} + +impl_instance!(0, 1, 2, 3, 4, 5, 6, 7); + +impl Sealed for T {} + +/// io configuration trait for Uart Tx configuration +pub trait TxPin: Pin + Sealed + PeripheralType { + /// convert the pin to appropriate function for Uart Tx usage + fn as_tx(&self); +} + +/// io configuration trait for Uart Rx configuration +pub trait RxPin: Pin + Sealed + PeripheralType { + /// convert the pin to appropriate function for Uart Rx usage + fn as_rx(&self); +} + +/// io configuration trait for Uart Cts +pub trait CtsPin: Pin + Sealed + PeripheralType { + /// convert the pin to appropriate function for Uart Cts usage + fn as_cts(&self); +} + +/// io configuration trait for Uart Rts +pub trait RtsPin: Pin + Sealed + PeripheralType { + /// convert the pin to appropriate function for Uart Rts usage + fn as_rts(&self); +} + +macro_rules! impl_pin_trait { + ($fcn:ident, $mode:ident, $($pin:ident, $fn:ident),*) => { + paste! { + $( + impl [<$mode:camel Pin>] for crate::peripherals::$pin { + fn [](&self) { + // UM11147 table 507 pg 495 + self.set_function(crate::iopctl::Function::$fn) + .set_pull(Pull::None) + .enable_input_buffer() + .set_slew_rate(SlewRate::Standard) + .set_drive_strength(DriveStrength::Normal) + .disable_analog_multiplex() + .set_drive_mode(DriveMode::PushPull) + .set_input_inverter(Inverter::Disabled); + } + } + )* + } + }; +} + +// FLEXCOMM0 +impl_pin_trait!(FLEXCOMM0, tx, PIO0_1, F1, PIO3_1, F5); +impl_pin_trait!(FLEXCOMM0, rx, PIO0_2, F1, PIO3_2, F5); +impl_pin_trait!(FLEXCOMM0, cts, PIO0_3, F1, PIO3_3, F5); +impl_pin_trait!(FLEXCOMM0, rts, PIO0_4, F1, PIO3_4, F5); + +// FLEXCOMM1 +impl_pin_trait!(FLEXCOMM1, tx, PIO0_8, F1, PIO7_26, F1); +impl_pin_trait!(FLEXCOMM1, rx, PIO0_9, F1, PIO7_27, F1); +impl_pin_trait!(FLEXCOMM1, cts, PIO0_10, F1, PIO7_28, F1); +impl_pin_trait!(FLEXCOMM1, rts, PIO0_11, F1, PIO7_29, F1); + +// FLEXCOMM2 +impl_pin_trait!(FLEXCOMM2, tx, PIO0_15, F1, PIO7_30, F5); +impl_pin_trait!(FLEXCOMM2, rx, PIO0_16, F1, PIO7_31, F5); +impl_pin_trait!(FLEXCOMM2, cts, PIO0_17, F1, PIO4_8, F5); +impl_pin_trait!(FLEXCOMM2, rts, PIO0_18, F1); + +// FLEXCOMM3 +impl_pin_trait!(FLEXCOMM3, tx, PIO0_22, F1); +impl_pin_trait!(FLEXCOMM3, rx, PIO0_23, F1); +impl_pin_trait!(FLEXCOMM3, cts, PIO0_24, F1); +impl_pin_trait!(FLEXCOMM3, rts, PIO0_25, F1); + +// FLEXCOMM4 +impl_pin_trait!(FLEXCOMM4, tx, PIO0_29, F1); +impl_pin_trait!(FLEXCOMM4, rx, PIO0_30, F1); +impl_pin_trait!(FLEXCOMM4, cts, PIO0_31, F1); +impl_pin_trait!(FLEXCOMM4, rts, PIO1_0, F1); + +// FLEXCOMM5 +impl_pin_trait!(FLEXCOMM5, tx, PIO1_4, F1, PIO3_16, F5); +impl_pin_trait!(FLEXCOMM5, rx, PIO1_5, F1, PIO3_17, F5); +impl_pin_trait!(FLEXCOMM5, cts, PIO1_6, F1, PIO3_18, F5); +impl_pin_trait!(FLEXCOMM5, rts, PIO1_7, F1, PIO3_23, F5); + +// FLEXCOMM6 +impl_pin_trait!(FLEXCOMM6, tx, PIO3_26, F1); +impl_pin_trait!(FLEXCOMM6, rx, PIO3_27, F1); +impl_pin_trait!(FLEXCOMM6, cts, PIO3_28, F1); +impl_pin_trait!(FLEXCOMM6, rts, PIO3_29, F1); + +// FLEXCOMM7 +impl_pin_trait!(FLEXCOMM7, tx, PIO4_1, F1); +impl_pin_trait!(FLEXCOMM7, rx, PIO4_2, F1); +impl_pin_trait!(FLEXCOMM7, cts, PIO4_3, F1); +impl_pin_trait!(FLEXCOMM7, rts, PIO4_4, F1); + +/// UART Tx DMA trait. +#[allow(private_bounds)] +pub trait TxDma: crate::dma::Channel {} + +/// UART Rx DMA trait. +#[allow(private_bounds)] +pub trait RxDma: crate::dma::Channel {} + +macro_rules! impl_dma { + ($fcn:ident, $mode:ident, $dma:ident) => { + paste! { + impl [<$mode Dma>] for crate::peripherals::$dma {} + } + }; +} + +impl_dma!(FLEXCOMM0, Rx, DMA0_CH0); +impl_dma!(FLEXCOMM0, Tx, DMA0_CH1); + +impl_dma!(FLEXCOMM1, Rx, DMA0_CH2); +impl_dma!(FLEXCOMM1, Tx, DMA0_CH3); + +impl_dma!(FLEXCOMM2, Rx, DMA0_CH4); +impl_dma!(FLEXCOMM2, Tx, DMA0_CH5); + +impl_dma!(FLEXCOMM3, Rx, DMA0_CH6); +impl_dma!(FLEXCOMM3, Tx, DMA0_CH7); + +impl_dma!(FLEXCOMM4, Rx, DMA0_CH8); +impl_dma!(FLEXCOMM4, Tx, DMA0_CH9); + +impl_dma!(FLEXCOMM5, Rx, DMA0_CH10); +impl_dma!(FLEXCOMM5, Tx, DMA0_CH11); + +impl_dma!(FLEXCOMM6, Rx, DMA0_CH12); +impl_dma!(FLEXCOMM6, Tx, DMA0_CH13); + +impl_dma!(FLEXCOMM7, Rx, DMA0_CH14); +impl_dma!(FLEXCOMM7, Tx, DMA0_CH15); diff --git a/embassy-imxrt/src/fmt.rs b/embassy-imxrt/src/fmt.rs new file mode 100644 index 000000000..c65273340 --- /dev/null +++ b/embassy-imxrt/src/fmt.rs @@ -0,0 +1,257 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(feature = "defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl Debug for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl Display for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl LowerHex for Bytes<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Bytes<'_> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-imxrt/src/gpio.rs b/embassy-imxrt/src/gpio.rs new file mode 100644 index 000000000..e62fde8b1 --- /dev/null +++ b/embassy-imxrt/src/gpio.rs @@ -0,0 +1,1042 @@ +//! GPIO + +use core::convert::Infallible; +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::interrupt::InterruptExt; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::clocks::enable_and_reset; +use crate::iopctl::IopctlPin; +pub use crate::iopctl::{AnyPin, DriveMode, DriveStrength, Function, Inverter, Pull, SlewRate}; +use crate::sealed::Sealed; +use crate::{interrupt, peripherals, BitIter, Peri, PeripheralType}; + +// This should be unique per IMXRT package +const PORT_COUNT: usize = 8; + +/// Digital input or output level. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + /// Logic Low + Low, + /// Logic High + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +/// Interrupt trigger levels. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InterruptType { + /// Trigger on level. + Level, + /// Trigger on edge. + Edge, +} + +#[cfg(feature = "rt")] +#[interrupt] +#[allow(non_snake_case)] +fn GPIO_INTA() { + irq_handler(&GPIO_WAKERS); +} + +#[cfg(feature = "rt")] +fn irq_handler(port_wakers: &[Option<&PortWaker>]) { + let reg = unsafe { crate::pac::Gpio::steal() }; + + for (port, port_waker) in port_wakers.iter().enumerate() { + let Some(port_waker) = port_waker else { + continue; + }; + + let stat = reg.intstata(port).read().bits(); + for pin in BitIter(stat) { + // Clear the interrupt from this pin + reg.intstata(port).write(|w| unsafe { w.status().bits(1 << pin) }); + // Disable interrupt from this pin + reg.intena(port) + .modify(|r, w| unsafe { w.int_en().bits(r.int_en().bits() & !(1 << pin)) }); + + let Some(waker) = port_waker.get_waker(pin as usize) else { + continue; + }; + + waker.wake(); + } + } +} + +/// Initialization Logic +/// Note: GPIO port clocks are initialized in the clocks module. +pub(crate) fn init() { + // Enable GPIO clocks + enable_and_reset::(); + enable_and_reset::(); + enable_and_reset::(); + enable_and_reset::(); + enable_and_reset::(); + enable_and_reset::(); + enable_and_reset::(); + enable_and_reset::(); + + // Enable INTA + interrupt::GPIO_INTA.unpend(); + + // SAFETY: + // + // At this point, all GPIO interrupts are masked. No interrupts + // will trigger until a pin is configured as Input, which can only + // happen after initialization of the HAL + unsafe { interrupt::GPIO_INTA.enable() }; +} + +/// Input Sense mode. +pub trait Sense: Sealed {} + +/// Sense Enabled Flex pin. +/// +/// This is a true flex pin as the input buffer is enabled. +/// It can sense its own level when even when configured as an output pin. +pub enum SenseEnabled {} +impl Sealed for SenseEnabled {} +impl Sense for SenseEnabled {} + +/// Sense Enabled Flex pin. +/// +/// This is **not** a true flex pin as the input buffer is disabled. +/// It cannot be turned into an input and it cannot see its own state, but it consumes less power. +/// Consider using a sense enabled flex pin if you need to read the pin's state or turn this into an input, +/// however note that **power consumption may be increased**. +pub enum SenseDisabled {} +impl Sealed for SenseDisabled {} +impl Sense for SenseDisabled {} + +/// Flex pin. +/// +/// 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, S: Sense> { + pin: Peri<'d, AnyPin>, + _sense_mode: PhantomData, +} + +impl Flex<'_, S> { + /// Converts pin to output pin + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + pub fn set_as_output(&mut self, mode: DriveMode, strength: DriveStrength, slew_rate: SlewRate) { + self.pin + .set_pull(Pull::None) + .set_drive_mode(mode) + .set_drive_strength(strength) + .set_slew_rate(slew_rate); + + self.pin.block().dirset(self.pin.port()).write(|w| + // SAFETY: Writing a 0 to bits in this register has no effect, + // however PAC has it marked unsafe due to using the bits() method. + // There is not currently a "safe" method for setting a single-bit. + unsafe { w.dirsetp().bits(1 << self.pin.pin()) }); + } + + /// Set high + pub fn set_high(&mut self) { + self.pin.block().set(self.pin.port()).write(|w| + // SAFETY: Writing a 0 to bits in this register has no effect, + // however PAC has it marked unsafe due to using the bits() method. + // There is not currently a "safe" method for setting a single-bit. + unsafe { w.setp().bits(1 << self.pin.pin()) }); + } + + /// Set low + pub fn set_low(&mut self) { + self.pin.block().clr(self.pin.port()).write(|w| + // SAFETY: Writing a 0 to bits in this register has no effect, + // however PAC has it marked unsafe due to using the bits() method. + // There is not currently a "safe" method for setting a single-bit. + unsafe { w.clrp().bits(1 << self.pin.pin()) }); + } + + /// Set level + pub fn set_level(&mut self, level: Level) { + match level { + Level::High => self.set_high(), + Level::Low => self.set_low(), + } + } + + /// Is the output level high? + #[must_use] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + /// Is the output level low? + #[must_use] + pub fn is_set_low(&self) -> bool { + (self.pin.block().set(self.pin.port()).read().setp().bits() & (1 << self.pin.pin())) == 0 + } + + /// Toggle + pub fn toggle(&mut self) { + self.pin.block().not(self.pin.port()).write(|w| + // SAFETY: Writing a 0 to bits in this register has no effect, + // however PAC has it marked unsafe due to using the bits() method. + // There is not currently a "safe" method for setting a single-bit. + unsafe { w.notp().bits(1 << self.pin.pin()) }); + } +} + +impl Drop for Flex<'_, S> { + #[inline] + fn drop(&mut self) { + critical_section::with(|_| { + self.pin.reset(); + }); + } +} + +impl<'d> Flex<'d, SenseEnabled> { + /// New flex pin. + pub fn new_with_sense(pin: Peri<'d, impl GpioPin>) -> Self { + pin.set_function(Function::F0) + .disable_analog_multiplex() + .enable_input_buffer(); + + Self { + pin: pin.into(), + _sense_mode: PhantomData::, + } + } + + /// Converts pin to input pin + pub fn set_as_input(&mut self, pull: Pull, inverter: Inverter) { + self.pin.set_pull(pull).set_input_inverter(inverter); + + self.pin.block().dirclr(self.pin.port()).write(|w| + // SAFETY: Writing a 0 to bits in this register has no effect, + // however PAC has it marked unsafe due to using the bits() method. + // There is not currently a "safe" method for setting a single-bit. + unsafe { w.dirclrp().bits(1 << self.pin.pin()) }); + } + + /// Converts pin to special function pin + /// # Safety + /// Unsafe to require justifying change from default to a special function + /// + pub unsafe fn set_as_special_function(&mut self, func: Function) { + self.pin.set_function(func); + } + + /// Is high? + #[must_use] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + /// Is low? + #[must_use] + pub fn is_low(&self) -> bool { + self.pin.block().b(self.pin.port()).b_(self.pin.pin()).read() == 0 + } + + /// Current level + #[must_use] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptType::Level, Level::High).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(self.pin.reborrow(), InterruptType::Level, Level::Low).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(self.pin.reborrow(), InterruptType::Edge, Level::High).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(self.pin.reborrow(), InterruptType::Edge, Level::Low).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) { + if self.is_high() { + InputFuture::new(self.pin.reborrow(), InterruptType::Edge, Level::Low).await; + } else { + InputFuture::new(self.pin.reborrow(), InterruptType::Edge, Level::High).await; + } + } + + /// Return a new Flex pin instance with level sensing disabled. + /// + /// Consumes less power than a flex pin with sensing enabled. + #[must_use] + pub fn disable_sensing(self) -> Flex<'d, SenseDisabled> { + // Cloning the pin is ok since we consume self immediately + let new_pin = unsafe { self.pin.clone_unchecked() }; + drop(self); + Flex::::new(new_pin) + } +} + +impl<'d> Flex<'d, SenseDisabled> { + /// New flex pin. + pub fn new(pin: Peri<'d, impl GpioPin>) -> Self { + pin.set_function(Function::F0) + .disable_analog_multiplex() + .disable_input_buffer(); + + Self { + pin: pin.into(), + _sense_mode: PhantomData::, + } + } + + /// Return a new Flex pin instance with level sensing enabled. + #[must_use] + pub fn enable_sensing(self) -> Flex<'d, SenseEnabled> { + // Cloning the pin is ok since we consume self immediately + let new_pin = unsafe { self.pin.clone_unchecked() }; + drop(self); + Flex::new_with_sense(new_pin) + } +} + +/// Input pin +pub struct Input<'d> { + // When Input is dropped, Flex's drop() will make sure the pin is reset to its default state. + pin: Flex<'d, SenseEnabled>, +} + +impl<'d> Input<'d> { + /// New input pin + pub fn new(pin: Peri<'d, impl GpioPin>, pull: Pull, inverter: Inverter) -> Self { + let mut pin = Flex::new_with_sense(pin); + pin.set_as_input(pull, inverter); + Self { pin } + } + + /// Is high? + #[must_use] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Is low? + #[must_use] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Input level + #[must_use] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await; + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await; + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await; + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await; + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await; + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + pin: Peri<'d, AnyPin>, +} + +impl<'d> InputFuture<'d> { + fn new(pin: Peri<'d, impl GpioPin>, int_type: InterruptType, level: Level) -> Self { + critical_section::with(|_| { + // Clear any existing pending interrupt on this pin + pin.block() + .intstata(pin.port()) + .write(|w| unsafe { w.status().bits(1 << pin.pin()) }); + + /* Pin interrupt configuration */ + pin.block().intedg(pin.port()).modify(|r, w| match int_type { + InterruptType::Edge => unsafe { w.bits(r.bits() | (1 << pin.pin())) }, + InterruptType::Level => unsafe { w.bits(r.bits() & !(1 << pin.pin())) }, + }); + + pin.block().intpol(pin.port()).modify(|r, w| match level { + Level::High => unsafe { w.bits(r.bits() & !(1 << pin.pin())) }, + Level::Low => unsafe { w.bits(r.bits() | (1 << pin.pin())) }, + }); + + // Enable pin interrupt on GPIO INT A + pin.block() + .intena(pin.port()) + .modify(|r, w| unsafe { w.int_en().bits(r.int_en().bits() | (1 << pin.pin())) }); + }); + + Self { pin: pin.into() } + } +} + +impl Future for InputFuture<'_> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + if self.pin.port() >= GPIO_WAKERS.len() { + panic!("Invalid GPIO port index {}", self.pin.port()); + } + + let port_waker = GPIO_WAKERS[self.pin.port()]; + if port_waker.is_none() { + panic!("Waker not present for GPIO port {}", self.pin.port()); + } + + let waker = port_waker.unwrap().get_waker(self.pin.pin()); + if waker.is_none() { + panic!( + "Waker not present for GPIO pin {}, port {}", + self.pin.pin(), + self.pin.port() + ); + } + waker.unwrap().register(cx.waker()); + + // Double check that the pin interrut has been disabled by IRQ handler + if self.pin.block().intena(self.pin.port()).read().bits() & (1 << self.pin.pin()) == 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +/// Output pin +/// Cannot be set as an input and cannot read its own pin state! +/// Consider using a Flex pin if you want that functionality, at the cost of higher power consumption. +pub struct Output<'d> { + // When Output is dropped, Flex's drop() will make sure the pin is reset to its default state. + pin: Flex<'d, SenseDisabled>, +} + +impl<'d> Output<'d> { + /// New output pin + pub fn new( + pin: Peri<'d, impl GpioPin>, + initial_output: Level, + mode: DriveMode, + strength: DriveStrength, + slew_rate: SlewRate, + ) -> Self { + let mut pin = Flex::new(pin); + pin.set_level(initial_output); + pin.set_as_output(mode, strength, slew_rate); + + Self { pin } + } + + /// Set high + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set low + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Toggle + pub fn toggle(&mut self) { + self.pin.toggle(); + } + + /// Set level + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level); + } + + /// Is set high? + #[must_use] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Is set low? + #[must_use] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } +} + +trait SealedPin: IopctlPin { + fn pin_port(&self) -> usize; + + fn port(&self) -> usize { + self.pin_port() / 32 + } + + fn pin(&self) -> usize { + self.pin_port() % 32 + } + + fn block(&self) -> crate::pac::Gpio { + // SAFETY: Assuming GPIO pin specific registers are only accessed through this HAL, + // this is safe because the HAL ensures ownership or exclusive mutable references + // to pins. + unsafe { crate::pac::Gpio::steal() } + } +} + +/// GPIO pin trait. +#[allow(private_bounds)] +pub trait GpioPin: SealedPin + Sized + PeripheralType + Into + 'static { + /// Type-erase the pin. + fn degrade(self) -> AnyPin { + // SAFETY: This is only called within the GpioPin trait, which is only + // implemented within this module on valid pin peripherals and thus + // has been verified to be correct. + unsafe { AnyPin::steal(self.port() as u8, self.pin() as u8) } + } +} + +impl SealedPin for AnyPin { + fn pin_port(&self) -> usize { + self.pin_port() + } +} +impl GpioPin for AnyPin {} + +macro_rules! impl_pin { + ($pin_periph:ident, $pin_port:expr, $pin_no:expr) => { + impl SealedPin for crate::peripherals::$pin_periph { + fn pin_port(&self) -> usize { + $pin_port * 32 + $pin_no + } + } + impl GpioPin for crate::peripherals::$pin_periph {} + impl From for AnyPin { + fn from(value: crate::peripherals::$pin_periph) -> Self { + value.degrade() + } + } + }; +} + +/// Container for pin wakers +struct PortWaker { + offset: usize, + wakers: &'static [AtomicWaker], +} + +impl PortWaker { + fn get_waker(&self, pin: usize) -> Option<&AtomicWaker> { + self.wakers.get(pin - self.offset) + } +} + +macro_rules! define_port_waker { + ($name:ident, $start:expr, $end:expr) => { + mod $name { + static PIN_WAKERS: [super::AtomicWaker; $end - $start + 1] = + [const { super::AtomicWaker::new() }; $end - $start + 1]; + pub static WAKER: super::PortWaker = super::PortWaker { + offset: $start, + wakers: &PIN_WAKERS, + }; + } + }; +} + +// GPIO port 0 +define_port_waker!(port0_waker, 0, 31); +impl_pin!(PIO0_0, 0, 0); +impl_pin!(PIO0_1, 0, 1); +impl_pin!(PIO0_2, 0, 2); +impl_pin!(PIO0_3, 0, 3); +impl_pin!(PIO0_4, 0, 4); +impl_pin!(PIO0_5, 0, 5); +impl_pin!(PIO0_6, 0, 6); +impl_pin!(PIO0_7, 0, 7); +impl_pin!(PIO0_8, 0, 8); +impl_pin!(PIO0_9, 0, 9); +impl_pin!(PIO0_10, 0, 10); +impl_pin!(PIO0_11, 0, 11); +impl_pin!(PIO0_12, 0, 12); +impl_pin!(PIO0_13, 0, 13); +impl_pin!(PIO0_14, 0, 14); +impl_pin!(PIO0_15, 0, 15); +impl_pin!(PIO0_16, 0, 16); +impl_pin!(PIO0_17, 0, 17); +impl_pin!(PIO0_18, 0, 18); +impl_pin!(PIO0_19, 0, 19); +impl_pin!(PIO0_20, 0, 20); +impl_pin!(PIO0_21, 0, 21); +impl_pin!(PIO0_22, 0, 22); +impl_pin!(PIO0_23, 0, 23); +impl_pin!(PIO0_24, 0, 24); +impl_pin!(PIO0_25, 0, 25); +impl_pin!(PIO0_26, 0, 26); +impl_pin!(PIO0_27, 0, 27); +impl_pin!(PIO0_28, 0, 28); +impl_pin!(PIO0_29, 0, 29); +impl_pin!(PIO0_30, 0, 30); +impl_pin!(PIO0_31, 0, 31); + +// GPIO port 1 +define_port_waker!(port1_waker, 0, 31); +impl_pin!(PIO1_0, 1, 0); +impl_pin!(PIO1_1, 1, 1); +impl_pin!(PIO1_2, 1, 2); +impl_pin!(PIO1_3, 1, 3); +impl_pin!(PIO1_4, 1, 4); +impl_pin!(PIO1_5, 1, 5); +impl_pin!(PIO1_6, 1, 6); +impl_pin!(PIO1_7, 1, 7); +impl_pin!(PIO1_8, 1, 8); +impl_pin!(PIO1_9, 1, 9); +impl_pin!(PIO1_10, 1, 10); +impl_pin!(PIO1_11, 1, 11); +impl_pin!(PIO1_12, 1, 12); +impl_pin!(PIO1_13, 1, 13); +impl_pin!(PIO1_14, 1, 14); +impl_pin!(PIO1_15, 1, 15); +impl_pin!(PIO1_16, 1, 16); +impl_pin!(PIO1_17, 1, 17); +impl_pin!(PIO1_18, 1, 18); +impl_pin!(PIO1_19, 1, 19); +impl_pin!(PIO1_20, 1, 20); +impl_pin!(PIO1_21, 1, 21); +impl_pin!(PIO1_22, 1, 22); +impl_pin!(PIO1_23, 1, 23); +impl_pin!(PIO1_24, 1, 24); +impl_pin!(PIO1_25, 1, 25); +impl_pin!(PIO1_26, 1, 26); +impl_pin!(PIO1_27, 1, 27); +impl_pin!(PIO1_28, 1, 28); +impl_pin!(PIO1_29, 1, 29); +impl_pin!(PIO1_30, 1, 30); +impl_pin!(PIO1_31, 1, 31); + +// GPIO port 2 +define_port_waker!(port2_waker, 0, 31); +impl_pin!(PIO2_0, 2, 0); +impl_pin!(PIO2_1, 2, 1); +impl_pin!(PIO2_2, 2, 2); +impl_pin!(PIO2_3, 2, 3); +impl_pin!(PIO2_4, 2, 4); +impl_pin!(PIO2_5, 2, 5); +impl_pin!(PIO2_6, 2, 6); +impl_pin!(PIO2_7, 2, 7); +impl_pin!(PIO2_8, 2, 8); +impl_pin!(PIO2_9, 2, 9); +impl_pin!(PIO2_10, 2, 10); +impl_pin!(PIO2_11, 2, 11); +impl_pin!(PIO2_12, 2, 12); +impl_pin!(PIO2_13, 2, 13); +impl_pin!(PIO2_14, 2, 14); +impl_pin!(PIO2_15, 2, 15); +impl_pin!(PIO2_16, 2, 16); +impl_pin!(PIO2_17, 2, 17); +impl_pin!(PIO2_18, 2, 18); +impl_pin!(PIO2_19, 2, 19); +impl_pin!(PIO2_20, 2, 20); +impl_pin!(PIO2_21, 2, 21); +impl_pin!(PIO2_22, 2, 22); +impl_pin!(PIO2_23, 2, 23); +impl_pin!(PIO2_24, 2, 24); +impl_pin!(PIO2_25, 2, 25); +impl_pin!(PIO2_26, 2, 26); +impl_pin!(PIO2_27, 2, 27); +impl_pin!(PIO2_28, 2, 28); +impl_pin!(PIO2_29, 2, 29); +impl_pin!(PIO2_30, 2, 30); +impl_pin!(PIO2_31, 2, 31); + +// GPIO port 3 +define_port_waker!(port3_waker, 0, 31); +impl_pin!(PIO3_0, 3, 0); +impl_pin!(PIO3_1, 3, 1); +impl_pin!(PIO3_2, 3, 2); +impl_pin!(PIO3_3, 3, 3); +impl_pin!(PIO3_4, 3, 4); +impl_pin!(PIO3_5, 3, 5); +impl_pin!(PIO3_6, 3, 6); +impl_pin!(PIO3_7, 3, 7); +impl_pin!(PIO3_8, 3, 8); +impl_pin!(PIO3_9, 3, 9); +impl_pin!(PIO3_10, 3, 10); +impl_pin!(PIO3_11, 3, 11); +impl_pin!(PIO3_12, 3, 12); +impl_pin!(PIO3_13, 3, 13); +impl_pin!(PIO3_14, 3, 14); +impl_pin!(PIO3_15, 3, 15); +impl_pin!(PIO3_16, 3, 16); +impl_pin!(PIO3_17, 3, 17); +impl_pin!(PIO3_18, 3, 18); +impl_pin!(PIO3_19, 3, 19); +impl_pin!(PIO3_20, 3, 20); +impl_pin!(PIO3_21, 3, 21); +impl_pin!(PIO3_22, 3, 22); +impl_pin!(PIO3_23, 3, 23); +impl_pin!(PIO3_24, 3, 24); +impl_pin!(PIO3_25, 3, 25); +impl_pin!(PIO3_26, 3, 26); +impl_pin!(PIO3_27, 3, 27); +impl_pin!(PIO3_28, 3, 28); +impl_pin!(PIO3_29, 3, 29); +impl_pin!(PIO3_30, 3, 30); +impl_pin!(PIO3_31, 3, 31); + +// GPIO port 4 +define_port_waker!(port4_waker, 0, 10); +impl_pin!(PIO4_0, 4, 0); +impl_pin!(PIO4_1, 4, 1); +impl_pin!(PIO4_2, 4, 2); +impl_pin!(PIO4_3, 4, 3); +impl_pin!(PIO4_4, 4, 4); +impl_pin!(PIO4_5, 4, 5); +impl_pin!(PIO4_6, 4, 6); +impl_pin!(PIO4_7, 4, 7); +impl_pin!(PIO4_8, 4, 8); +impl_pin!(PIO4_9, 4, 9); +impl_pin!(PIO4_10, 4, 10); + +// GPIO port 7 +define_port_waker!(port7_waker, 24, 31); +impl_pin!(PIO7_24, 7, 24); +impl_pin!(PIO7_25, 7, 25); +impl_pin!(PIO7_26, 7, 26); +impl_pin!(PIO7_27, 7, 27); +impl_pin!(PIO7_28, 7, 28); +impl_pin!(PIO7_29, 7, 29); +impl_pin!(PIO7_30, 7, 30); +impl_pin!(PIO7_31, 7, 31); + +static GPIO_WAKERS: [Option<&PortWaker>; PORT_COUNT] = [ + Some(&port0_waker::WAKER), + Some(&port1_waker::WAKER), + Some(&port2_waker::WAKER), + Some(&port3_waker::WAKER), + Some(&port4_waker::WAKER), + None, + None, + Some(&port7_waker::WAKER), +]; + +impl embedded_hal_02::digital::v2::InputPin for Flex<'_, SenseEnabled> { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl embedded_hal_02::digital::v2::OutputPin for Flex<'_, S> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'_, SenseEnabled> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'_, S> { + type Error = Infallible; + + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl embedded_hal_02::digital::v2::InputPin for Input<'_> { + type Error = Infallible; + + #[inline] + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + #[inline] + fn is_low(&self) -> Result { + Ok(self.is_low()) + } +} + +impl embedded_hal_02::digital::v2::OutputPin for Output<'_> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_02::digital::v2::StatefulOutputPin for Output<'_> { + #[inline] + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + #[inline] + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } +} + +impl embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'_> { + type Error = Infallible; + + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + self.toggle(); + Ok(()) + } +} + +impl embedded_hal_1::digital::ErrorType for Flex<'_, S> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::InputPin for Flex<'_, SenseEnabled> { + #[inline] + fn is_high(&mut self) -> Result { + // Dereference of self is used here and a few other places to + // access the correct method (since different types/traits + // share method names) + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl embedded_hal_1::digital::OutputPin for Flex<'_, S> { + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_1::digital::StatefulOutputPin for Flex<'_, SenseEnabled> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Flex<'d, SenseEnabled> { + #[inline] + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + #[inline] + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + #[inline] + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + #[inline] + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + #[inline] + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl embedded_hal_1::digital::ErrorType for Input<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::InputPin for Input<'_> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Input<'d> { + #[inline] + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + #[inline] + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + #[inline] + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + #[inline] + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + #[inline] + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl embedded_hal_1::digital::ErrorType for Output<'_> { + type Error = Infallible; +} + +impl embedded_hal_1::digital::OutputPin for Output<'_> { + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + self.set_high(); + Ok(()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + self.set_low(); + Ok(()) + } +} + +impl embedded_hal_1::digital::StatefulOutputPin for Output<'_> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} diff --git a/embassy-imxrt/src/iopctl.rs b/embassy-imxrt/src/iopctl.rs new file mode 100644 index 000000000..a3b8b14d6 --- /dev/null +++ b/embassy-imxrt/src/iopctl.rs @@ -0,0 +1,717 @@ +//! IO Pad Controller (IOPCTL) +//! +//! Also known as IO Pin Configuration (IOCON) + +use crate::pac::{iopctl, Iopctl}; + +// A generic pin of any type. +// +// The actual pin type used here is arbitrary, +// as all PioM_N types provide the same methods. +// +// Merely need some pin type to cast a raw pointer +// to in order to access the provided methods. +#[allow(non_camel_case_types)] +type PioM_N = iopctl::Pio0_0; + +/// Pin function number. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Function { + /// Function 0 + F0, + /// Function 1 + F1, + /// Function 2 + F2, + /// Function 3 + F3, + /// Function 4 + F4, + /// Function 5 + F5, + /// Function 6 + F6, + /// Function 7 + F7, + /// Function 8 + F8, +} + +/// Internal pull-up/down resistors on a pin. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pull { + /// No pull-up or pull-down resistor selected + None, + /// Pull-up resistor + Up, + /// Pull-down resistor + Down, +} + +/// Pin slew rate. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SlewRate { + /// Standard slew rate + Standard, + /// Slow slew rate + Slow, +} + +/// Output drive strength of a pin. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DriveStrength { + /// Normal + Normal, + /// Full + Full, +} + +/// Output drive mode of a pin. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DriveMode { + /// Push-Pull + PushPull, + /// Pseudo Open-Drain + OpenDrain, +} + +/// Input inverter of a pin. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Inverter { + /// No inverter + Disabled, + /// Enable input inverter on the input port. A low signal will be + /// seen as a high signal by the pin. + Enabled, +} + +trait SealedPin {} +trait ToAnyPin: SealedPin { + #[inline] + fn to_raw(port: u8, pin: u8) -> AnyPin { + // SAFETY: This is safe since this is only called from within the module, + // where the port and pin numbers have been verified to be correct. + unsafe { AnyPin::steal(port, pin) } + } +} + +trait ToFC15Pin: SealedPin { + #[inline] + fn to_raw(pin: u8) -> FC15Pin { + // SAFETY: This is safe since this is only called from within the module, + // where the port and pin numbers have been verified to be correct. + unsafe { FC15Pin::steal(pin) } + } +} + +/// A pin that can be configured via iopctl. +#[allow(private_bounds)] +pub trait IopctlPin: SealedPin { + /// Sets the function number of a pin. + /// + /// This number corresponds to a specific function that the pin supports. + /// + /// Typically, function 0 corresponds to GPIO while other numbers correspond to a special function. + /// + /// See Section 7.5.3 in reference manual for list of pins and their supported functions. + fn set_function(&self, function: Function) -> &Self; + + /// Enables either a pull-up or pull-down resistor on a pin. + /// + /// Setting this to [`Pull::None`] will disable the resistor. + fn set_pull(&self, pull: Pull) -> &Self; + + /// Enables the input buffer of a pin. + /// + /// This must be enabled for any pin acting as an input, + /// and some peripheral pins acting as output may need this enabled as well. + /// + /// If there is any doubt, it is best to enable the input buffer. + /// + /// See Section 7.4.2.3 of reference manual. + fn enable_input_buffer(&self) -> &Self; + + /// Disables the input buffer of a pin. + fn disable_input_buffer(&self) -> &Self; + + /// Sets the slew rate of a pin. + /// + /// This controls the speed at which a pin can toggle, + /// which is voltage and load dependent. + fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self; + + /// Sets the output drive strength of a pin. + /// + /// A drive strength of [`DriveStrength::Full`] has twice the + /// high and low drive capability of the [`DriveStrength::Normal`] setting. + fn set_drive_strength(&self, strength: DriveStrength) -> &Self; + + /// Enables the analog multiplexer of a pin. + /// + /// This must be called to allow analog functionalities of a pin. + /// + /// To protect the analog input, [`IopctlPin::set_function`] should be + /// called with [`Function::F0`] to disable digital functions. + /// + /// Additionally, [`IopctlPin::disable_input_buffer`] and [`IopctlPin::set_pull`] + /// with [`Pull::None`] should be called. + fn enable_analog_multiplex(&self) -> &Self; + + /// Disables the analog multiplexer of a pin. + fn disable_analog_multiplex(&self) -> &Self; + + /// Sets the ouput drive mode of a pin. + /// + /// A pin configured as [`DriveMode::OpenDrain`] actually operates in + /// a "pseudo" open-drain mode which is somewhat different than true open-drain. + /// + /// See Section 7.4.2.7 of reference manual. + fn set_drive_mode(&self, mode: DriveMode) -> &Self; + + /// Sets the input inverter of an input pin. + /// + /// Setting this to [`Inverter::Enabled`] will invert + /// the input signal. + fn set_input_inverter(&self, inverter: Inverter) -> &Self; + + /// Returns a pin to its reset state. + fn reset(&self) -> &Self; +} + +/// Represents a pin peripheral created at run-time from given port and pin numbers. +pub struct AnyPin { + pin_port: u8, + reg: &'static PioM_N, +} + +impl AnyPin { + /// Creates a pin from raw port and pin numbers which can then be configured. + /// + /// This should ONLY be called when there is no other choice + /// (e.g. from a type-erased GPIO pin). + /// + /// Otherwise, pin peripherals should be configured directly. + /// + /// # Safety + /// + /// The caller MUST ensure valid port and pin numbers are provided, + /// and that multiple instances of [`AnyPin`] with the same port + /// and pin combination are not being used simultaneously. + /// + /// Failure to uphold these requirements will result in undefined behavior. + /// + /// See Table 297 in reference manual for a list of valid + /// pin and port number combinations. + #[must_use] + pub unsafe fn steal(port: u8, pin: u8) -> Self { + // Calculates the offset from the beginning of the IOPCTL register block + // address to the register address representing the pin. + // + // See Table 297 in reference manual for how this offset is calculated. + let offset = ((port as usize) << 7) + ((pin as usize) << 2); + + // SAFETY: This is safe assuming the caller of this function satisfies the safety requirements above. + let reg = unsafe { &*Iopctl::ptr().byte_offset(offset as isize).cast() }; + Self { + pin_port: port * 32 + pin, + reg, + } + } + + /// Returns the pin's port and pin combination. + #[must_use] + pub fn pin_port(&self) -> usize { + self.pin_port as usize + } +} + +/// Represents a FC15 pin peripheral created at run-time from given pin number. +pub struct FC15Pin { + reg: &'static PioM_N, +} + +impl FC15Pin { + /// Creates an FC15 pin from raw pin number which can then be configured. + /// + /// This should ONLY be called when there is no other choice + /// (e.g. from a type-erased GPIO pin). + /// + /// Otherwise, pin peripherals should be configured directly. + /// + /// # Safety + /// + /// The caller MUST ensure valid port and pin numbers are provided, + /// and that multiple instances of [`AnyPin`] with the same port + /// and pin combination are not being used simultaneously. + /// + /// Failure to uphold these requirements will result in undefined behavior. + /// + /// See Table 297 in reference manual for a list of valid + /// pin and port number combinations. + #[must_use] + pub unsafe fn steal(pin: u8) -> Self { + // Table 297: FC15_I2C_SCL offset = 0x400, FC15_I2C_SCL offset = 0x404 + let iopctl = unsafe { crate::pac::Iopctl::steal() }; + + let reg = if pin == 0 { + &*iopctl.fc15_i2c_scl().as_ptr().cast() + } else { + &*iopctl.fc15_i2c_sda().as_ptr().cast() + }; + + Self { reg } + } +} + +// This allows AnyPin/FC15Pin to be used in HAL constructors that require types +// which impl Peripheral. Used primarily by GPIO HAL to convert type-erased +// GPIO pins back into an Output or Input pin specifically. +embassy_hal_internal::impl_peripheral!(AnyPin); + +impl SealedPin for AnyPin {} + +embassy_hal_internal::impl_peripheral!(FC15Pin); + +impl SealedPin for FC15Pin {} + +macro_rules! impl_iopctlpin { + ($pintype:ident) => { + impl IopctlPin for $pintype { + fn set_function(&self, function: Function) -> &Self { + critical_section::with(|_| match function { + Function::F0 => { + self.reg.modify(|_, w| w.fsel().function_0()); + } + Function::F1 => { + self.reg.modify(|_, w| w.fsel().function_1()); + } + Function::F2 => { + self.reg.modify(|_, w| w.fsel().function_2()); + } + Function::F3 => { + self.reg.modify(|_, w| w.fsel().function_3()); + } + Function::F4 => { + self.reg.modify(|_, w| w.fsel().function_4()); + } + Function::F5 => { + self.reg.modify(|_, w| w.fsel().function_5()); + } + Function::F6 => { + self.reg.modify(|_, w| w.fsel().function_6()); + } + Function::F7 => { + self.reg.modify(|_, w| w.fsel().function_7()); + } + Function::F8 => { + self.reg.modify(|_, w| w.fsel().function_8()); + } + }); + self + } + + fn set_pull(&self, pull: Pull) -> &Self { + critical_section::with(|_| { + match pull { + Pull::None => { + self.reg.modify(|_, w| w.pupdena().disabled()); + } + Pull::Up => { + self.reg.modify(|_, w| w.pupdena().enabled().pupdsel().pull_up()); + } + Pull::Down => { + self.reg + .modify(|_, w| w.pupdena().enabled().pupdsel().pull_down()); + } + } + self + }) + } + + fn enable_input_buffer(&self) -> &Self { + critical_section::with(|_| self.reg.modify(|_, w| w.ibena().enabled())); + self + } + + fn disable_input_buffer(&self) -> &Self { + critical_section::with(|_| self.reg.modify(|_, w| w.ibena().disabled())); + self + } + + fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self { + critical_section::with(|_| match slew_rate { + SlewRate::Standard => { + self.reg.modify(|_, w| w.slewrate().normal()); + } + SlewRate::Slow => { + self.reg.modify(|_, w| w.slewrate().slow()); + } + }); + self + } + + fn set_drive_strength(&self, strength: DriveStrength) -> &Self { + critical_section::with(|_| match strength { + DriveStrength::Normal => { + self.reg.modify(|_, w| w.fulldrive().normal_drive()); + } + DriveStrength::Full => { + self.reg.modify(|_, w| w.fulldrive().full_drive()); + } + }); + self + } + + fn enable_analog_multiplex(&self) -> &Self { + critical_section::with(|_| self.reg.modify(|_, w| w.amena().enabled())); + self + } + + fn disable_analog_multiplex(&self) -> &Self { + critical_section::with(|_| self.reg.modify(|_, w| w.amena().disabled())); + self + } + + fn set_drive_mode(&self, mode: DriveMode) -> &Self { + critical_section::with(|_| match mode { + DriveMode::PushPull => { + self.reg.modify(|_, w| w.odena().disabled()); + } + DriveMode::OpenDrain => { + self.reg.modify(|_, w| w.odena().enabled()); + } + }); + self + } + + fn set_input_inverter(&self, inverter: Inverter) -> &Self { + critical_section::with(|_| match inverter { + Inverter::Disabled => { + self.reg.modify(|_, w| w.iiena().disabled()); + } + Inverter::Enabled => { + self.reg.modify(|_, w| w.iiena().enabled()); + } + }); + self + } + + fn reset(&self) -> &Self { + self.reg.reset(); + self + } + } + }; +} + +impl_iopctlpin!(AnyPin); +impl_iopctlpin!(FC15Pin); + +macro_rules! impl_FC15pin { + ($pin_periph:ident, $pin_no:expr) => { + impl SealedPin for crate::peripherals::$pin_periph {} + impl ToFC15Pin for crate::peripherals::$pin_periph {} + impl IopctlPin for crate::peripherals::$pin_periph { + #[inline] + fn set_function(&self, _function: Function) -> &Self { + //No function configuration for FC15 pin + self + } + + #[inline] + fn set_pull(&self, pull: Pull) -> &Self { + Self::to_raw($pin_no).set_pull(pull); + self + } + + #[inline] + fn enable_input_buffer(&self) -> &Self { + Self::to_raw($pin_no).enable_input_buffer(); + self + } + + #[inline] + fn disable_input_buffer(&self) -> &Self { + Self::to_raw($pin_no).disable_input_buffer(); + self + } + + #[inline] + fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self { + Self::to_raw($pin_no).set_slew_rate(slew_rate); + self + } + + #[inline] + fn set_drive_strength(&self, strength: DriveStrength) -> &Self { + Self::to_raw($pin_no).set_drive_strength(strength); + self + } + + #[inline] + fn enable_analog_multiplex(&self) -> &Self { + Self::to_raw($pin_no).enable_analog_multiplex(); + self + } + + #[inline] + fn disable_analog_multiplex(&self) -> &Self { + Self::to_raw($pin_no).disable_analog_multiplex(); + self + } + + #[inline] + fn set_drive_mode(&self, mode: DriveMode) -> &Self { + Self::to_raw($pin_no).set_drive_mode(mode); + self + } + + #[inline] + fn set_input_inverter(&self, inverter: Inverter) -> &Self { + Self::to_raw($pin_no).set_input_inverter(inverter); + self + } + + #[inline] + fn reset(&self) -> &Self { + Self::to_raw($pin_no).reset(); + self + } + } + }; +} + +macro_rules! impl_pin { + ($pin_periph:ident, $pin_port:expr, $pin_no:expr) => { + impl SealedPin for crate::peripherals::$pin_periph {} + impl ToAnyPin for crate::peripherals::$pin_periph {} + impl IopctlPin for crate::peripherals::$pin_periph { + #[inline] + fn set_function(&self, function: Function) -> &Self { + Self::to_raw($pin_port, $pin_no).set_function(function); + self + } + + #[inline] + fn set_pull(&self, pull: Pull) -> &Self { + Self::to_raw($pin_port, $pin_no).set_pull(pull); + self + } + + #[inline] + fn enable_input_buffer(&self) -> &Self { + Self::to_raw($pin_port, $pin_no).enable_input_buffer(); + self + } + + #[inline] + fn disable_input_buffer(&self) -> &Self { + Self::to_raw($pin_port, $pin_no).disable_input_buffer(); + self + } + + #[inline] + fn set_slew_rate(&self, slew_rate: SlewRate) -> &Self { + Self::to_raw($pin_port, $pin_no).set_slew_rate(slew_rate); + self + } + + #[inline] + fn set_drive_strength(&self, strength: DriveStrength) -> &Self { + Self::to_raw($pin_port, $pin_no).set_drive_strength(strength); + self + } + + #[inline] + fn enable_analog_multiplex(&self) -> &Self { + Self::to_raw($pin_port, $pin_no).enable_analog_multiplex(); + self + } + + #[inline] + fn disable_analog_multiplex(&self) -> &Self { + Self::to_raw($pin_port, $pin_no).disable_analog_multiplex(); + self + } + + #[inline] + fn set_drive_mode(&self, mode: DriveMode) -> &Self { + Self::to_raw($pin_port, $pin_no).set_drive_mode(mode); + self + } + + #[inline] + fn set_input_inverter(&self, inverter: Inverter) -> &Self { + Self::to_raw($pin_port, $pin_no).set_input_inverter(inverter); + self + } + + #[inline] + fn reset(&self) -> &Self { + Self::to_raw($pin_port, $pin_no).reset(); + self + } + } + }; +} + +impl_pin!(PIO0_0, 0, 0); +impl_pin!(PIO0_1, 0, 1); +impl_pin!(PIO0_2, 0, 2); +impl_pin!(PIO0_3, 0, 3); +impl_pin!(PIO0_4, 0, 4); +impl_pin!(PIO0_5, 0, 5); +impl_pin!(PIO0_6, 0, 6); +impl_pin!(PIO0_7, 0, 7); +impl_pin!(PIO0_8, 0, 8); +impl_pin!(PIO0_9, 0, 9); +impl_pin!(PIO0_10, 0, 10); +impl_pin!(PIO0_11, 0, 11); +impl_pin!(PIO0_12, 0, 12); +impl_pin!(PIO0_13, 0, 13); +impl_pin!(PIO0_14, 0, 14); +impl_pin!(PIO0_15, 0, 15); +impl_pin!(PIO0_16, 0, 16); +impl_pin!(PIO0_17, 0, 17); +impl_pin!(PIO0_18, 0, 18); +impl_pin!(PIO0_19, 0, 19); +impl_pin!(PIO0_20, 0, 20); +impl_pin!(PIO0_21, 0, 21); +impl_pin!(PIO0_22, 0, 22); +impl_pin!(PIO0_23, 0, 23); +impl_pin!(PIO0_24, 0, 24); +impl_pin!(PIO0_25, 0, 25); +impl_pin!(PIO0_26, 0, 26); +impl_pin!(PIO0_27, 0, 27); +impl_pin!(PIO0_28, 0, 28); +impl_pin!(PIO0_29, 0, 29); +impl_pin!(PIO0_30, 0, 30); +impl_pin!(PIO0_31, 0, 31); +impl_pin!(PIO1_0, 1, 0); +impl_pin!(PIO1_1, 1, 1); +impl_pin!(PIO1_2, 1, 2); +impl_pin!(PIO1_3, 1, 3); +impl_pin!(PIO1_4, 1, 4); +impl_pin!(PIO1_5, 1, 5); +impl_pin!(PIO1_6, 1, 6); +impl_pin!(PIO1_7, 1, 7); +impl_pin!(PIO1_8, 1, 8); +impl_pin!(PIO1_9, 1, 9); +impl_pin!(PIO1_10, 1, 10); +impl_pin!(PIO1_11, 1, 11); +impl_pin!(PIO1_12, 1, 12); +impl_pin!(PIO1_13, 1, 13); +impl_pin!(PIO1_14, 1, 14); +impl_pin!(PIO1_15, 1, 15); +impl_pin!(PIO1_16, 1, 16); +impl_pin!(PIO1_17, 1, 17); +impl_pin!(PIO1_18, 1, 18); +impl_pin!(PIO1_19, 1, 19); +impl_pin!(PIO1_20, 1, 20); +impl_pin!(PIO1_21, 1, 21); +impl_pin!(PIO1_22, 1, 22); +impl_pin!(PIO1_23, 1, 23); +impl_pin!(PIO1_24, 1, 24); +impl_pin!(PIO1_25, 1, 25); +impl_pin!(PIO1_26, 1, 26); +impl_pin!(PIO1_27, 1, 27); +impl_pin!(PIO1_28, 1, 28); +impl_pin!(PIO1_29, 1, 29); +impl_pin!(PIO1_30, 1, 30); +impl_pin!(PIO1_31, 1, 31); +impl_pin!(PIO2_0, 2, 0); +impl_pin!(PIO2_1, 2, 1); +impl_pin!(PIO2_2, 2, 2); +impl_pin!(PIO2_3, 2, 3); +impl_pin!(PIO2_4, 2, 4); +impl_pin!(PIO2_5, 2, 5); +impl_pin!(PIO2_6, 2, 6); +impl_pin!(PIO2_7, 2, 7); +impl_pin!(PIO2_8, 2, 8); +impl_pin!(PIO2_9, 2, 9); +impl_pin!(PIO2_10, 2, 10); +impl_pin!(PIO2_11, 2, 11); +impl_pin!(PIO2_12, 2, 12); +impl_pin!(PIO2_13, 2, 13); +impl_pin!(PIO2_14, 2, 14); +impl_pin!(PIO2_15, 2, 15); +impl_pin!(PIO2_16, 2, 16); +impl_pin!(PIO2_17, 2, 17); +impl_pin!(PIO2_18, 2, 18); +impl_pin!(PIO2_19, 2, 19); +impl_pin!(PIO2_20, 2, 20); +impl_pin!(PIO2_21, 2, 21); +impl_pin!(PIO2_22, 2, 22); +impl_pin!(PIO2_23, 2, 23); +impl_pin!(PIO2_24, 2, 24); + +// Note: These have have reset values of 0x41 to support SWD by default +impl_pin!(PIO2_25, 2, 25); +impl_pin!(PIO2_26, 2, 26); + +impl_pin!(PIO2_27, 2, 27); +impl_pin!(PIO2_28, 2, 28); +impl_pin!(PIO2_29, 2, 29); +impl_pin!(PIO2_30, 2, 30); +impl_pin!(PIO2_31, 2, 31); +impl_pin!(PIO3_0, 3, 0); +impl_pin!(PIO3_1, 3, 1); +impl_pin!(PIO3_2, 3, 2); +impl_pin!(PIO3_3, 3, 3); +impl_pin!(PIO3_4, 3, 4); +impl_pin!(PIO3_5, 3, 5); +impl_pin!(PIO3_6, 3, 6); +impl_pin!(PIO3_7, 3, 7); +impl_pin!(PIO3_8, 3, 8); +impl_pin!(PIO3_9, 3, 9); +impl_pin!(PIO3_10, 3, 10); +impl_pin!(PIO3_11, 3, 11); +impl_pin!(PIO3_12, 3, 12); +impl_pin!(PIO3_13, 3, 13); +impl_pin!(PIO3_14, 3, 14); +impl_pin!(PIO3_15, 3, 15); +impl_pin!(PIO3_16, 3, 16); +impl_pin!(PIO3_17, 3, 17); +impl_pin!(PIO3_18, 3, 18); +impl_pin!(PIO3_19, 3, 19); +impl_pin!(PIO3_20, 3, 20); +impl_pin!(PIO3_21, 3, 21); +impl_pin!(PIO3_22, 3, 22); +impl_pin!(PIO3_23, 3, 23); +impl_pin!(PIO3_24, 3, 24); +impl_pin!(PIO3_25, 3, 25); +impl_pin!(PIO3_26, 3, 26); +impl_pin!(PIO3_27, 3, 27); +impl_pin!(PIO3_28, 3, 28); +impl_pin!(PIO3_29, 3, 29); +impl_pin!(PIO3_30, 3, 30); +impl_pin!(PIO3_31, 3, 31); +impl_pin!(PIO4_0, 4, 0); +impl_pin!(PIO4_1, 4, 1); +impl_pin!(PIO4_2, 4, 2); +impl_pin!(PIO4_3, 4, 3); +impl_pin!(PIO4_4, 4, 4); +impl_pin!(PIO4_5, 4, 5); +impl_pin!(PIO4_6, 4, 6); +impl_pin!(PIO4_7, 4, 7); +impl_pin!(PIO4_8, 4, 8); +impl_pin!(PIO4_9, 4, 9); +impl_pin!(PIO4_10, 4, 10); +impl_pin!(PIO7_24, 7, 24); +impl_pin!(PIO7_25, 7, 25); +impl_pin!(PIO7_26, 7, 26); +impl_pin!(PIO7_27, 7, 27); +impl_pin!(PIO7_28, 7, 28); +impl_pin!(PIO7_29, 7, 29); +impl_pin!(PIO7_30, 7, 30); +impl_pin!(PIO7_31, 7, 31); + +// FC15 pins +impl_FC15pin!(PIOFC15_SCL, 0); +impl_FC15pin!(PIOFC15_SDA, 1); diff --git a/embassy-imxrt/src/lib.rs b/embassy-imxrt/src/lib.rs new file mode 100644 index 000000000..5846afe5c --- /dev/null +++ b/embassy-imxrt/src/lib.rs @@ -0,0 +1,173 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +#[cfg(not(any(feature = "mimxrt633s", feature = "mimxrt685s",)))] +compile_error!( + "No chip feature activated. You must activate exactly one of the following features: + mimxrt633s, + mimxrt685s, + " +); + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +pub mod clocks; +pub mod crc; +pub mod dma; +pub mod flexcomm; +pub mod gpio; +pub mod iopctl; +pub mod rng; + +#[cfg(feature = "_time-driver")] +pub mod time_driver; + +// This mod MUST go last, so that it sees all the `impl_foo!' macros +#[cfg_attr(feature = "mimxrt633s", path = "chips/mimxrt633s.rs")] +#[cfg_attr(feature = "mimxrt685s", path = "chips/mimxrt685s.rs")] +mod chip; + +// Reexports +pub use chip::interrupts::*; +#[cfg(feature = "unstable-pac")] +pub use chip::pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use chip::pac; +pub use chip::{peripherals, Peripherals}; +pub use embassy_hal_internal::{Peri, PeripheralType}; + +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right \[`Binding`\]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_imxrt::{bind_interrupts, flexspi, peripherals}; +/// +/// bind_interrupts!( +/// /// Binds the FLEXSPI interrupt. +/// struct Irqs { +/// FLEXSPI_IRQ => flexspi::InterruptHandler; +/// } +/// ); +/// ``` +/// +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($(#[$attr:meta])* $vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + unsafe extern "C" fn $irq() { + $( + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* + } + + $( + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + )* + }; +} + +/// HAL configuration for iMX RT600. +pub mod config { + use crate::clocks::ClockConfig; + + /// HAL configuration passed when initializing. + #[non_exhaustive] + pub struct Config { + /// Clock configuration. + pub clocks: ClockConfig, + + /// RTC Time driver interrupt priority. + #[cfg(feature = "_time-driver")] + pub time_interrupt_priority: crate::interrupt::Priority, + } + + impl Default for Config { + fn default() -> Self { + Self { + clocks: ClockConfig::crystal(), + #[cfg(feature = "_time-driver")] + time_interrupt_priority: crate::interrupt::Priority::P0, + } + } + } + + impl Config { + /// Create a new configuration with the provided clock config. + pub fn new(clocks: ClockConfig) -> Self { + Self { + clocks, + #[cfg(feature = "_time-driver")] + time_interrupt_priority: crate::interrupt::Priority::P0, + } + } + } +} + +/// Initialize the `embassy-imxrt` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +pub fn init(config: config::Config) -> Peripherals { + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + #[cfg(feature = "_time-driver")] + time_driver::init(config.time_interrupt_priority); + + unsafe { + if let Err(e) = clocks::init(config.clocks) { + error!("unable to initialize Clocks for reason: {:?}", e); + // Panic here? + } + dma::init(); + } + gpio::init(); + + peripherals +} + +pub(crate) mod sealed { + pub trait Sealed {} +} + +#[cfg(feature = "rt")] +struct BitIter(u32); + +#[cfg(feature = "rt")] +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +} diff --git a/embassy-imxrt/src/rng.rs b/embassy-imxrt/src/rng.rs new file mode 100644 index 000000000..75f243df9 --- /dev/null +++ b/embassy-imxrt/src/rng.rs @@ -0,0 +1,287 @@ +//! True Random Number Generator (TRNG) + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_futures::block_on; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::clocks::{enable_and_reset, SysconPeripheral}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, peripherals, Peri, PeripheralType}; + +static RNG_WAKER: AtomicWaker = AtomicWaker::new(); + +/// RNG ;error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Seed error. + SeedError, + + /// HW Error. + HwError, + + /// Frequency Count Fail + FreqCountFail, +} + +/// RNG interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::info().regs; + let int_status = regs.int_status().read(); + + if int_status.ent_val().bit_is_set() + || int_status.hw_err().bit_is_set() + || int_status.frq_ct_fail().bit_is_set() + { + regs.int_ctrl().modify(|_, w| { + w.ent_val() + .ent_val_0() + .hw_err() + .hw_err_0() + .frq_ct_fail() + .frq_ct_fail_0() + }); + RNG_WAKER.wake(); + } + } +} + +/// RNG driver. +pub struct Rng<'d> { + info: Info, + _lifetime: PhantomData<&'d ()>, +} + +impl<'d> Rng<'d> { + /// Create a new RNG driver. + pub fn new( + _inner: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + enable_and_reset::(); + + let mut random = Self { + info: T::info(), + _lifetime: PhantomData, + }; + random.init(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + random + } + + /// Reset the RNG. + pub fn reset(&mut self) { + self.info.regs.mctl().write(|w| w.rst_def().set_bit().prgm().set_bit()); + } + + /// Fill the given slice with random values. + pub async fn async_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + // We have a total of 16 words (512 bits) of entropy at our + // disposal. The idea here is to read all bits and copy the + // necessary bytes to the slice. + for chunk in dest.chunks_mut(64) { + self.async_fill_chunk(chunk).await?; + } + + Ok(()) + } + + async fn async_fill_chunk(&mut self, chunk: &mut [u8]) -> Result<(), Error> { + // wait for interrupt + let res = poll_fn(|cx| { + // Check if already ready. + // TODO: Is this necessary? Could we just check once after + // the waker has been registered? + if self.info.regs.int_status().read().ent_val().bit_is_set() { + return Poll::Ready(Ok(())); + } + + RNG_WAKER.register(cx.waker()); + + self.unmask_interrupts(); + + let mctl = self.info.regs.mctl().read(); + + // Check again if interrupt fired + if mctl.ent_val().bit_is_set() { + Poll::Ready(Ok(())) + } else if mctl.err().bit_is_set() { + Poll::Ready(Err(Error::HwError)) + } else if mctl.fct_fail().bit_is_set() { + Poll::Ready(Err(Error::FreqCountFail)) + } else { + Poll::Pending + } + }) + .await; + + let bits = self.info.regs.mctl().read(); + + if bits.ent_val().bit_is_set() { + let mut entropy = [0; 16]; + + for (i, item) in entropy.iter_mut().enumerate() { + *item = self.info.regs.ent(i).read().bits(); + } + + // Read MCTL after reading ENT15 + let _ = self.info.regs.mctl().read(); + + if entropy.iter().any(|e| *e == 0) { + return Err(Error::SeedError); + } + + // SAFETY: entropy is the same for input and output types in + // native endianness. + let entropy: [u8; 64] = unsafe { core::mem::transmute(entropy) }; + + // write bytes to chunk + chunk.copy_from_slice(&entropy[..chunk.len()]); + } + + res + } + + fn mask_interrupts(&mut self) { + self.info.regs.int_mask().write(|w| { + w.ent_val() + .ent_val_0() + .hw_err() + .hw_err_0() + .frq_ct_fail() + .frq_ct_fail_0() + }); + } + + fn unmask_interrupts(&mut self) { + self.info.regs.int_mask().modify(|_, w| { + w.ent_val() + .ent_val_1() + .hw_err() + .hw_err_1() + .frq_ct_fail() + .frq_ct_fail_1() + }); + } + + fn enable_interrupts(&mut self) { + self.info.regs.int_ctrl().write(|w| { + w.ent_val() + .ent_val_1() + .hw_err() + .hw_err_1() + .frq_ct_fail() + .frq_ct_fail_1() + }); + } + + fn init(&mut self) { + self.mask_interrupts(); + + // Switch TRNG to programming mode + self.info.regs.mctl().modify(|_, w| w.prgm().set_bit()); + + self.enable_interrupts(); + + // Switch TRNG to Run Mode + self.info + .regs + .mctl() + .modify(|_, w| w.trng_acc().set_bit().prgm().clear_bit()); + } + + /// Generate a random u32 + pub fn blocking_next_u32(&mut self) -> u32 { + let mut bytes = [0u8; 4]; + block_on(self.async_fill_bytes(&mut bytes)).unwrap(); + u32::from_ne_bytes(bytes) + } + + /// Generate a random u64 + pub fn blocking_next_u64(&mut self) -> u64 { + let mut bytes = [0u8; 8]; + block_on(self.async_fill_bytes(&mut bytes)).unwrap(); + u64::from_ne_bytes(bytes) + } + + /// Fill a slice with random bytes. + pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { + block_on(self.async_fill_bytes(dest)).unwrap(); + } +} + +impl<'d> rand_core_06::RngCore for Rng<'d> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} + +impl<'d> rand_core_06::CryptoRng for Rng<'d> {} + +impl<'d> rand_core_09::RngCore for Rng<'d> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } +} + +impl<'d> rand_core_09::CryptoRng for Rng<'d> {} + +struct Info { + regs: crate::pac::Trng, +} + +trait SealedInstance { + fn info() -> Info; +} + +/// RNG instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + SysconPeripheral + 'static + Send { + /// Interrupt for this RNG instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +impl Instance for peripherals::RNG { + type Interrupt = crate::interrupt::typelevel::RNG; +} + +impl SealedInstance for peripherals::RNG { + fn info() -> Info { + // SAFETY: safe from single executor + Info { + regs: unsafe { crate::pac::Trng::steal() }, + } + } +} diff --git a/embassy-imxrt/src/time_driver.rs b/embassy-imxrt/src/time_driver.rs new file mode 100644 index 000000000..f127609c8 --- /dev/null +++ b/embassy-imxrt/src/time_driver.rs @@ -0,0 +1,413 @@ +//! Time Driver. +use core::cell::{Cell, RefCell}; +#[cfg(feature = "time-driver-rtc")] +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; + +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; + +#[cfg(feature = "time-driver-os-timer")] +use crate::clocks::enable; +use crate::interrupt::InterruptExt; +use crate::{interrupt, pac}; + +struct AlarmState { + timestamp: Cell, +} + +unsafe impl Send for AlarmState {} + +impl AlarmState { + const fn new() -> Self { + Self { + timestamp: Cell::new(u64::MAX), + } + } +} + +#[cfg(feature = "time-driver-rtc")] +fn rtc() -> &'static pac::rtc::RegisterBlock { + unsafe { &*pac::Rtc::ptr() } +} + +/// Calculate the timestamp from the period count and the tick count. +/// +/// To get `now()`, `period` is read first, then `counter` is read. If the counter value matches +/// the expected range for the `period` parity, we're done. If it doesn't, this means that +/// a new period start has raced us between reading `period` and `counter`, so we assume the `counter` value +/// corresponds to the next period. +/// +/// the 1kHz RTC counter is 16 bits and RTC doesn't have separate compare channels, +/// so using a 32 bit GPREG0-2 as counter, compare, and int_en +/// `period` is a 32bit integer, gpreg 'counter' is 31 bits plus the parity bit for overflow detection +#[cfg(feature = "time-driver-rtc")] +fn calc_now(period: u32, counter: u32) -> u64 { + ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64) +} + +#[cfg(feature = "time-driver-rtc")] +embassy_time_driver::time_driver_impl!(static DRIVER: Rtc = Rtc { + period: AtomicU32::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); + +#[cfg(feature = "time-driver-rtc")] +struct Rtc { + /// Number of 2^31 periods elapsed since boot. + period: AtomicU32, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, + queue: Mutex>, +} + +#[cfg(feature = "time-driver-rtc")] +impl Rtc { + /// Access the GPREG0 register to use it as a 31-bit counter. + #[inline] + fn counter_reg(&self) -> &pac::rtc::Gpreg { + rtc().gpreg(0) + } + + /// Access the GPREG1 register to use it as a compare register for triggering alarms. + #[inline] + fn compare_reg(&self) -> &pac::rtc::Gpreg { + rtc().gpreg(1) + } + + /// Access the GPREG2 register to use it to enable or disable interrupts (int_en). + #[inline] + fn int_en_reg(&self) -> &pac::rtc::Gpreg { + rtc().gpreg(2) + } + + fn init(&'static self, irq_prio: crate::interrupt::Priority) { + let r = rtc(); + // enable RTC int (1kHz since subsecond doesn't generate an int) + r.ctrl().modify(|_r, w| w.rtc1khz_en().set_bit()); + // TODO: low power support. line above is leaving out write to .wakedpd_en().set_bit()) + // which enables wake from deep power down + + // safety: Writing to the gregs is always considered unsafe, gpreg1 is used + // as a compare register for triggering an alarm so to avoid unnecessary triggers + // after initialization, this is set to 0x:FFFF_FFFF + self.compare_reg().write(|w| unsafe { w.gpdata().bits(u32::MAX) }); + // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. + // The following loads 10 into the count-down timer. + r.wake().write(|w| unsafe { w.bits(0xA) }); + interrupt::RTC.set_priority(irq_prio); + unsafe { interrupt::RTC.enable() }; + } + + #[cfg(feature = "rt")] + fn on_interrupt(&self) { + let r = rtc(); + // This interrupt fires every 10 ticks of the 1kHz RTC high res clk and adds + // 10 to the 31 bit counter gpreg0. The 32nd bit is used for parity detection + // This is done to avoid needing to calculate # of ticks spent on interrupt + // handlers to recalibrate the clock between interrupts + // + // TODO: this is admittedly not great for power that we're generating this + // many interrupts, will probably get updated in future iterations. + if r.ctrl().read().wake1khz().bit_is_set() { + r.ctrl().modify(|_r, w| w.wake1khz().set_bit()); + // safety: writing a value to the 1kHz RTC wake counter is always considered unsafe. + // The following reloads 10 into the count-down timer after it triggers an int. + // The countdown begins anew after the write so time can continue to be measured. + r.wake().write(|w| unsafe { w.bits(0xA) }); + if (self.counter_reg().read().bits() + 0xA) > 0x8000_0000 { + // if we're going to "overflow", increase the period + self.next_period(); + let rollover_diff = 0x8000_0000 - (self.counter_reg().read().bits() + 0xA); + // safety: writing to gpregs is always considered unsafe. In order to + // not "lose" time when incrementing the period, gpreg0, the extended + // counter, is restarted at the # of ticks it would overflow by + self.counter_reg().write(|w| unsafe { w.bits(rollover_diff) }); + } else { + self.counter_reg().modify(|r, w| unsafe { w.bits(r.bits() + 0xA) }); + } + } + + critical_section::with(|cs| { + // gpreg2 as an "int_en" set by next_period(). This is + // 1 when the timestamp for the alarm deadline expires + // before the counter register overflows again. + if self.int_en_reg().read().gpdata().bits() == 1 { + // gpreg0 is our extended counter register, check if + // our counter is larger than the compare value + if self.counter_reg().read().bits() > self.compare_reg().read().bits() { + self.trigger_alarm(cs); + } + } + }) + } + + #[cfg(feature = "rt")] + fn next_period(&self) { + critical_section::with(|cs| { + let period = self + .period + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |p| Some(p + 1)) + .unwrap_or_else(|p| { + trace!("Unable to increment period. Time is now inaccurate"); + // TODO: additional error handling beyond logging + + p + }); + let t = (period as u64) << 31; + + let alarm = &self.alarms.borrow(cs); + let at = alarm.timestamp.get(); + if at < t + 0xc000_0000 { + // safety: writing to gpregs is always unsafe, gpreg2 is an alarm + // enable. If the alarm must trigger within the next period, then + // just enable it. `set_alarm` has already set the correct CC val. + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); + } + }) + } + + #[must_use] + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let alarm = self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + // safety: Writing to the gpregs is always unsafe, gpreg2 is + // always just used as the alarm enable for the timer driver. + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); + + alarm.timestamp.set(u64::MAX); + + return false; + } + + // If it hasn't triggered yet, setup it by writing to the compare field + // An alarm can be delayed, but this is allowed by the Alarm trait contract. + // What's not allowed is triggering alarms *before* their scheduled time, + let safe_timestamp = timestamp.max(t + 10); //t+3 was done for nrf chip, choosing 10 + + // safety: writing to the gregs is always unsafe. When a new alarm is set, + // the compare register, gpreg1, is set to the last 31 bits of the timestamp + // as the 32nd and final bit is used for the parity check in `next_period` + // `period` will be used for the upper bits in a timestamp comparison. + self.compare_reg() + .modify(|_r, w| unsafe { w.bits(safe_timestamp as u32 & 0x7FFF_FFFF) }); + + // The following checks that the difference in timestamp is less than the overflow period + let diff = timestamp - t; + if diff < 0xc000_0000 { + // this is 0b11 << (30). NRF chip used 23 bit periods and checked against 0b11<<22 + + // safety: writing to the gpregs is always unsafe. If the alarm + // must trigger within the next period, set the "int enable" + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(1) }); + } else { + // safety: writing to the gpregs is always unsafe. If alarm must trigger + // some time after the current period, too far in the future, don't setup + // the alarm enable, gpreg2, yet. It will be setup later by `next_period`. + self.int_en_reg().write(|w| unsafe { w.gpdata().bits(0) }); + } + + true + } + + #[cfg(feature = "rt")] + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } +} + +#[cfg(feature = "time-driver-rtc")] +impl Driver for Rtc { + fn now(&self) -> u64 { + // `period` MUST be read before `counter`, see comment at the top for details. + let period = self.period.load(Ordering::Acquire); + compiler_fence(Ordering::Acquire); + let counter = self.counter_reg().read().bits(); + calc_now(period, counter) + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +#[cfg(all(feature = "rt", feature = "time-driver-rtc"))] +#[allow(non_snake_case)] +#[interrupt] +fn RTC() { + DRIVER.on_interrupt() +} + +#[cfg(feature = "time-driver-os-timer")] +fn os() -> &'static pac::ostimer0::RegisterBlock { + unsafe { &*pac::Ostimer0::ptr() } +} + +/// Convert gray to decimal +/// +/// Os Event provides a 64-bit timestamp gray-encoded. All we have to +/// do here is read both 32-bit halves of the register and convert +/// from gray to regular binary. +#[cfg(feature = "time-driver-os-timer")] +fn gray_to_dec(gray: u64) -> u64 { + let mut dec = gray; + + dec ^= dec >> 1; + dec ^= dec >> 2; + dec ^= dec >> 4; + dec ^= dec >> 8; + dec ^= dec >> 16; + dec ^= dec >> 32; + + dec +} + +/// Convert decimal to gray +/// +/// Before writing match value to the target register, we must convert +/// it back into gray code. +#[cfg(feature = "time-driver-os-timer")] +fn dec_to_gray(dec: u64) -> u64 { + let gray = dec; + gray ^ (gray >> 1) +} + +#[cfg(feature = "time-driver-os-timer")] +embassy_time_driver::time_driver_impl!(static DRIVER: OsTimer = OsTimer { + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), +}); + +#[cfg(feature = "time-driver-os-timer")] +struct OsTimer { + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarms: Mutex, + queue: Mutex>, +} + +#[cfg(feature = "time-driver-os-timer")] +impl OsTimer { + fn init(&'static self, irq_prio: crate::interrupt::Priority) { + // init alarms + critical_section::with(|cs| { + let alarm = DRIVER.alarms.borrow(cs); + alarm.timestamp.set(u64::MAX); + }); + + // Enable clocks. Documentation advises AGAINST resetting this + // peripheral. + enable::(); + + interrupt::OS_EVENT.disable(); + + // Make sure interrupt is masked + os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); + + // Default to the end of time + os().match_l().write(|w| unsafe { w.bits(0xffff_ffff) }); + os().match_h().write(|w| unsafe { w.bits(0xffff_ffff) }); + + interrupt::OS_EVENT.unpend(); + interrupt::OS_EVENT.set_priority(irq_prio); + unsafe { interrupt::OS_EVENT.enable() }; + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let alarm = self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + // Wait until we're allowed to write to MATCH_L/MATCH_H + // registers + while os().osevent_ctrl().read().match_wr_rdy().bit_is_set() {} + + let t = self.now(); + if timestamp <= t { + os().osevent_ctrl().modify(|_, w| w.ostimer_intena().clear_bit()); + alarm.timestamp.set(u64::MAX); + return false; + } + + let gray_timestamp = dec_to_gray(timestamp); + + os().match_l() + .write(|w| unsafe { w.bits(gray_timestamp as u32 & 0xffff_ffff) }); + os().match_h() + .write(|w| unsafe { w.bits((gray_timestamp >> 32) as u32) }); + os().osevent_ctrl().modify(|_, w| w.ostimer_intena().set_bit()); + + true + } + + #[cfg(feature = "rt")] + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } + + #[cfg(feature = "rt")] + fn on_interrupt(&self) { + critical_section::with(|cs| { + if os().osevent_ctrl().read().ostimer_intrflag().bit_is_set() { + os().osevent_ctrl() + .modify(|_, w| w.ostimer_intena().clear_bit().ostimer_intrflag().set_bit()); + self.trigger_alarm(cs); + } + }); + } +} + +#[cfg(feature = "time-driver-os-timer")] +impl Driver for OsTimer { + fn now(&self) -> u64 { + let mut t = os().evtimerh().read().bits() as u64; + t <<= 32; + t |= os().evtimerl().read().bits() as u64; + gray_to_dec(t) + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +#[cfg(all(feature = "rt", feature = "time-driver-os-timer"))] +#[allow(non_snake_case)] +#[interrupt] +fn OS_EVENT() { + DRIVER.on_interrupt() +} + +pub(crate) fn init(irq_prio: crate::interrupt::Priority) { + DRIVER.init(irq_prio) +} diff --git a/embassy-mspm0/Cargo.toml b/embassy-mspm0/Cargo.toml new file mode 100644 index 000000000..456d302af --- /dev/null +++ b/embassy-mspm0/Cargo.toml @@ -0,0 +1,257 @@ +[package] +name = "embassy-mspm0" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for Texas Instruments MSPM0 series microcontrollers" +keywords = ["embedded", "async", "mspm0", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-mspm0" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-mspm0-v$VERSION/embassy-mspm0/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-mspm0/src/" + +features = ["defmt", "unstable-pac", "time-driver-any"] +flavors = [ + { regex_feature = "mspm0c.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "mspm0l.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "mspm0g.*", target = "thumbv6m-none-eabi" }, +] + +[package.metadata.docs.rs] +features = ["defmt", "unstable-pac", "time-driver-any", "time", "mspm0g3507"] +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } +# TODO: Support other tick rates +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true, features = ["tick-hz-32_768"] } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal", default-features = false } +embassy-executor = { version = "0.7.0", path = "../embassy-executor", optional = true } + +embedded-hal = { version = "1.0" } +embedded-hal-async = { version = "1.0" } + +defmt = { version = "1.0.1", optional = true } +fixed = "1.29" +log = { version = "0.4.14", optional = true } +cortex-m-rt = ">=0.6.15,<0.8" +cortex-m = "0.7.6" +critical-section = "1.2.0" + +# mspm0-metapac = { version = "" } +mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-26a6f681eda4ef120e8cb614a1631727c848590f" } + +[build-dependencies] +proc-macro2 = "1.0.94" +quote = "1.0.40" + +# mspm0-metapac = { version = "", default-features = false, features = ["metadata"] } +mspm0-metapac = { git = "https://github.com/mspm0-rs/mspm0-data-generated/", tag = "mspm0-data-26a6f681eda4ef120e8cb614a1631727c848590f", default-features = false, features = ["metadata"] } + +[features] +default = ["rt"] + +## Enable `mspm0-metapac`'s `rt` feature +rt = ["mspm0-metapac/rt"] + +## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging +defmt = [ + "dep:defmt", + "embassy-sync/defmt", + "embassy-embedded-hal/defmt", + "embassy-hal-internal/defmt", + "embassy-time?/defmt", +] + +## Re-export mspm0-metapac at `mspm0::pac`. +## This is unstable because semver-minor (non-breaking) releases of embassy-mspm0 may major-bump (breaking) the mspm0-metapac version. +## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. +## There are no plans to make this stable. +unstable-pac = [] + +#! ## Time + +# Features starting with `_` are for internal use only. They're not intended +# to be enabled by other crates, and are not covered by semver guarantees. +_time-driver = ["dep:embassy-time-driver", "dep:embassy-time-queue-utils"] + +# Use any time driver +time-driver-any = ["_time-driver"] +## Use TIMG0 as time driver +time-driver-timg0 = ["_time-driver"] +## Use TIMG1 as time driver +time-driver-timg1 = ["_time-driver"] +## Use TIMG2 as time driver +time-driver-timg2 = ["_time-driver"] +## Use TIMG3 as time driver +time-driver-timg3 = ["_time-driver"] +## Use TIMG4 as time driver +time-driver-timg4 = ["_time-driver"] +## Use TIMG5 as time driver +time-driver-timg5 = ["_time-driver"] +## Use TIMG6 as time driver +time-driver-timg6 = ["_time-driver"] +## Use TIMG7 as time driver +time-driver-timg7 = ["_time-driver"] +## Use TIMG8 as time driver +time-driver-timg8 = ["_time-driver"] +## Use TIMG9 as time driver +time-driver-timg9 = ["_time-driver"] +## Use TIMG10 as time driver +time-driver-timg10 = ["_time-driver"] +## Use TIMG11 as time driver +time-driver-timg11 = ["_time-driver"] +## Use TIMG12 as time driver +time-driver-timg12 = ["_time-driver"] +## Use TIMG13 as time driver +time-driver-timg13 = ["_time-driver"] +## Use TIMG14 as time driver +time-driver-timg14 = ["_time-driver"] +## Use TIMA0 as time driver +time-driver-tima0 = ["_time-driver"] +## Use TIMA1 as time driver +time-driver-tima1 = ["_time-driver"] + +#! ## Chip-selection features +#! Select your chip by specifying the model as a feature, e.g. `mspm0g3507pm`. +#! Check the `Cargo.toml` for the latest list of supported chips. +#! +#! **Important:** Do not forget to adapt the target chip in your toolchain, +#! e.g. in `.cargo/config.toml`. + +mspm0c1103dgs20 = ["mspm0-metapac/mspm0c1103dgs20"] +mspm0c1103dsg = ["mspm0-metapac/mspm0c1103dsg"] +mspm0c1103dyy = ["mspm0-metapac/mspm0c1103dyy"] +mspm0c1103ruk = ["mspm0-metapac/mspm0c1103ruk"] +mspm0c1104dgs20 = ["mspm0-metapac/mspm0c1104dgs20"] +mspm0c1104dsg = ["mspm0-metapac/mspm0c1104dsg"] +mspm0c1104dyy = ["mspm0-metapac/mspm0c1104dyy"] +mspm0c1104ruk = ["mspm0-metapac/mspm0c1104ruk"] +mspm0c1104ycj = ["mspm0-metapac/mspm0c1104ycj"] +mspm0g1105dgs28 = ["mspm0-metapac/mspm0g1105dgs28"] +mspm0g1105pm = ["mspm0-metapac/mspm0g1105pm"] +mspm0g1105pt = ["mspm0-metapac/mspm0g1105pt"] +mspm0g1105rge = ["mspm0-metapac/mspm0g1105rge"] +mspm0g1105rgz = ["mspm0-metapac/mspm0g1105rgz"] +mspm0g1105rhb = ["mspm0-metapac/mspm0g1105rhb"] +mspm0g1106dgs28 = ["mspm0-metapac/mspm0g1106dgs28"] +mspm0g1106pm = ["mspm0-metapac/mspm0g1106pm"] +mspm0g1106pt = ["mspm0-metapac/mspm0g1106pt"] +mspm0g1106rge = ["mspm0-metapac/mspm0g1106rge"] +mspm0g1106rgz = ["mspm0-metapac/mspm0g1106rgz"] +mspm0g1106rhb = ["mspm0-metapac/mspm0g1106rhb"] +mspm0g1107dgs28 = ["mspm0-metapac/mspm0g1107dgs28"] +mspm0g1107pm = ["mspm0-metapac/mspm0g1107pm"] +mspm0g1107pt = ["mspm0-metapac/mspm0g1107pt"] +mspm0g1107rge = ["mspm0-metapac/mspm0g1107rge"] +mspm0g1107rgz = ["mspm0-metapac/mspm0g1107rgz"] +mspm0g1107rhb = ["mspm0-metapac/mspm0g1107rhb"] +mspm0g1107ycj = ["mspm0-metapac/mspm0g1107ycj"] +mspm0g1505pm = ["mspm0-metapac/mspm0g1505pm"] +mspm0g1505pt = ["mspm0-metapac/mspm0g1505pt"] +mspm0g1505rge = ["mspm0-metapac/mspm0g1505rge"] +mspm0g1505rgz = ["mspm0-metapac/mspm0g1505rgz"] +mspm0g1505rhb = ["mspm0-metapac/mspm0g1505rhb"] +mspm0g1506pm = ["mspm0-metapac/mspm0g1506pm"] +mspm0g1506pt = ["mspm0-metapac/mspm0g1506pt"] +mspm0g1506rge = ["mspm0-metapac/mspm0g1506rge"] +mspm0g1506rgz = ["mspm0-metapac/mspm0g1506rgz"] +mspm0g1506rhb = ["mspm0-metapac/mspm0g1506rhb"] +mspm0g1507pm = ["mspm0-metapac/mspm0g1507pm"] +mspm0g1507pt = ["mspm0-metapac/mspm0g1507pt"] +mspm0g1507rge = ["mspm0-metapac/mspm0g1507rge"] +mspm0g1507rgz = ["mspm0-metapac/mspm0g1507rgz"] +mspm0g1507rhb = ["mspm0-metapac/mspm0g1507rhb"] +mspm0g1507ycj = ["mspm0-metapac/mspm0g1507ycj"] +mspm0g1519rgz = ["mspm0-metapac/mspm0g1519rgz"] +mspm0g1519rhb = ["mspm0-metapac/mspm0g1519rhb"] +mspm0g3105dgs20 = ["mspm0-metapac/mspm0g3105dgs20"] +mspm0g3105dgs28 = ["mspm0-metapac/mspm0g3105dgs28"] +mspm0g3105rhb = ["mspm0-metapac/mspm0g3105rhb"] +mspm0g3106dgs20 = ["mspm0-metapac/mspm0g3106dgs20"] +mspm0g3106dgs28 = ["mspm0-metapac/mspm0g3106dgs28"] +mspm0g3106rhb = ["mspm0-metapac/mspm0g3106rhb"] +mspm0g3107dgs20 = ["mspm0-metapac/mspm0g3107dgs20"] +mspm0g3107dgs28 = ["mspm0-metapac/mspm0g3107dgs28"] +mspm0g3107rhb = ["mspm0-metapac/mspm0g3107rhb"] +mspm0g3505dgs28 = ["mspm0-metapac/mspm0g3505dgs28"] +mspm0g3505pm = ["mspm0-metapac/mspm0g3505pm"] +mspm0g3505pt = ["mspm0-metapac/mspm0g3505pt"] +mspm0g3505rgz = ["mspm0-metapac/mspm0g3505rgz"] +mspm0g3505rhb = ["mspm0-metapac/mspm0g3505rhb"] +mspm0g3506dgs28 = ["mspm0-metapac/mspm0g3506dgs28"] +mspm0g3506pm = ["mspm0-metapac/mspm0g3506pm"] +mspm0g3506pt = ["mspm0-metapac/mspm0g3506pt"] +mspm0g3506rgz = ["mspm0-metapac/mspm0g3506rgz"] +mspm0g3506rhb = ["mspm0-metapac/mspm0g3506rhb"] +mspm0g3507dgs28 = ["mspm0-metapac/mspm0g3507dgs28"] +mspm0g3507pm = ["mspm0-metapac/mspm0g3507pm"] +mspm0g3507pt = ["mspm0-metapac/mspm0g3507pt"] +mspm0g3507rgz = ["mspm0-metapac/mspm0g3507rgz"] +mspm0g3507rhb = ["mspm0-metapac/mspm0g3507rhb"] +mspm0g3519pm = ["mspm0-metapac/mspm0g3519pm"] +mspm0g3519pn = ["mspm0-metapac/mspm0g3519pn"] +mspm0g3519pz = ["mspm0-metapac/mspm0g3519pz"] +mspm0g3519rgz = ["mspm0-metapac/mspm0g3519rgz"] +mspm0g3519rhb = ["mspm0-metapac/mspm0g3519rhb"] +mspm0l1105dgs20 = ["mspm0-metapac/mspm0l1105dgs20"] +mspm0l1105dgs28 = ["mspm0-metapac/mspm0l1105dgs28"] +mspm0l1105dyy = ["mspm0-metapac/mspm0l1105dyy"] +mspm0l1105rge = ["mspm0-metapac/mspm0l1105rge"] +mspm0l1105rtr = ["mspm0-metapac/mspm0l1105rtr"] +mspm0l1106dgs20 = ["mspm0-metapac/mspm0l1106dgs20"] +mspm0l1106dgs28 = ["mspm0-metapac/mspm0l1106dgs28"] +mspm0l1106dyy = ["mspm0-metapac/mspm0l1106dyy"] +mspm0l1106rge = ["mspm0-metapac/mspm0l1106rge"] +mspm0l1106rhb = ["mspm0-metapac/mspm0l1106rhb"] +mspm0l1106rtr = ["mspm0-metapac/mspm0l1106rtr"] +mspm0l1227pm = ["mspm0-metapac/mspm0l1227pm"] +mspm0l1227pn = ["mspm0-metapac/mspm0l1227pn"] +mspm0l1227pt = ["mspm0-metapac/mspm0l1227pt"] +mspm0l1227rge = ["mspm0-metapac/mspm0l1227rge"] +mspm0l1227rgz = ["mspm0-metapac/mspm0l1227rgz"] +mspm0l1227rhb = ["mspm0-metapac/mspm0l1227rhb"] +mspm0l1228pm = ["mspm0-metapac/mspm0l1228pm"] +mspm0l1228pn = ["mspm0-metapac/mspm0l1228pn"] +mspm0l1228pt = ["mspm0-metapac/mspm0l1228pt"] +mspm0l1228rge = ["mspm0-metapac/mspm0l1228rge"] +mspm0l1228rgz = ["mspm0-metapac/mspm0l1228rgz"] +mspm0l1228rhb = ["mspm0-metapac/mspm0l1228rhb"] +mspm0l1303rge = ["mspm0-metapac/mspm0l1303rge"] +mspm0l1304dgs20 = ["mspm0-metapac/mspm0l1304dgs20"] +mspm0l1304dgs28 = ["mspm0-metapac/mspm0l1304dgs28"] +mspm0l1304dyy = ["mspm0-metapac/mspm0l1304dyy"] +mspm0l1304rge = ["mspm0-metapac/mspm0l1304rge"] +mspm0l1304rhb = ["mspm0-metapac/mspm0l1304rhb"] +mspm0l1304rtr = ["mspm0-metapac/mspm0l1304rtr"] +mspm0l1305dgs20 = ["mspm0-metapac/mspm0l1305dgs20"] +mspm0l1305dgs28 = ["mspm0-metapac/mspm0l1305dgs28"] +mspm0l1305dyy = ["mspm0-metapac/mspm0l1305dyy"] +mspm0l1305rge = ["mspm0-metapac/mspm0l1305rge"] +mspm0l1305rtr = ["mspm0-metapac/mspm0l1305rtr"] +mspm0l1306dgs20 = ["mspm0-metapac/mspm0l1306dgs20"] +mspm0l1306dgs28 = ["mspm0-metapac/mspm0l1306dgs28"] +mspm0l1306dyy = ["mspm0-metapac/mspm0l1306dyy"] +mspm0l1306rge = ["mspm0-metapac/mspm0l1306rge"] +mspm0l1306rhb = ["mspm0-metapac/mspm0l1306rhb"] +mspm0l1343dgs20 = ["mspm0-metapac/mspm0l1343dgs20"] +mspm0l1344dgs20 = ["mspm0-metapac/mspm0l1344dgs20"] +mspm0l1345dgs28 = ["mspm0-metapac/mspm0l1345dgs28"] +mspm0l1346dgs28 = ["mspm0-metapac/mspm0l1346dgs28"] +mspm0l2227pm = ["mspm0-metapac/mspm0l2227pm"] +mspm0l2227pn = ["mspm0-metapac/mspm0l2227pn"] +mspm0l2227pt = ["mspm0-metapac/mspm0l2227pt"] +mspm0l2227rgz = ["mspm0-metapac/mspm0l2227rgz"] +mspm0l2228pm = ["mspm0-metapac/mspm0l2228pm"] +mspm0l2228pn = ["mspm0-metapac/mspm0l2228pn"] +mspm0l2228pt = ["mspm0-metapac/mspm0l2228pt"] +mspm0l2228rgz = ["mspm0-metapac/mspm0l2228rgz"] +msps003f3pw20 = ["mspm0-metapac/msps003f3pw20"] +msps003f4pw20 = ["mspm0-metapac/msps003f4pw20"] diff --git a/embassy-mspm0/README.md b/embassy-mspm0/README.md new file mode 100644 index 000000000..b2b8934aa --- /dev/null +++ b/embassy-mspm0/README.md @@ -0,0 +1,28 @@ +# Embassy MSPM0 HAL + +The embassy-mspm0 HAL aims to provide a safe, idiomatic hardware abstraction layer for all MSPM0 and MSPS003 chips. + +* [Documentation](https://docs.embassy.dev/embassy-mspm0/) (**Important:** use docs.embassy.dev rather than docs.rs to see the specific docs for the chip you’re using!) +* [Source](https://github.com/embassy-rs/embassy/tree/main/embassy-mspm0) +* [Examples](https://github.com/embassy-rs/embassy/tree/main/examples) + +## Embedded-hal + +The `embassy-mspm0` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (1.0) and [embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as [embedded-io](https://crates.io/crates/embedded-io) and [embedded-io-async](https://crates.io/crates/embedded-io-async). + +## A note on feature flag names + +Feature flag names for chips do not include temperature rating or distribution format. + +Usually chapter 10 of your device's datasheet will explain the device nomenclature and how to decode it. Feature names in embassy-mspm0 only use the following from device nomenclature: +- MCU platform +- Product family +- Device subfamily +- Flash memory +- Package type + +This means for a part such as `MSPM0G3507SPMR`, the feature name is `mspm0g3507pm`. This also means that `MSPM0G3507QPMRQ1` uses the feature `mspm0g3507pm`, since the Q1 parts are just qualified variants of the base G3507 with a PM (QFP-64) package. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-mspm0/build.rs b/embassy-mspm0/build.rs new file mode 100644 index 000000000..6cd62895b --- /dev/null +++ b/embassy-mspm0/build.rs @@ -0,0 +1,967 @@ +use std::cmp::Ordering; +use std::collections::{BTreeSet, HashMap}; +use std::fmt::Write; +use std::io::Write as _; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::sync::LazyLock; +use std::{env, fs}; + +use common::CfgSet; +use mspm0_metapac::metadata::{ALL_CHIPS, METADATA}; +use proc_macro2::{Ident, Literal, Span, TokenStream}; +use quote::{format_ident, quote}; + +#[path = "./build_common.rs"] +mod common; + +fn main() { + generate_code(); + interrupt_group_linker_magic(); +} + +fn generate_code() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); + + #[cfg(any(feature = "rt"))] + println!( + "cargo:rustc-link-search={}", + PathBuf::from(env::var_os("OUT_DIR").unwrap()).display(), + ); + + cfgs.declare_all(&["gpio_pb", "gpio_pc", "int_group1"]); + + let chip_name = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_MSPM0") || x.starts_with("CARGO_FEATURE_MSPS")) + .get_one() + { + Ok(x) => x, + Err(GetOneError::None) => panic!("No mspm0xx/mspsxx Cargo feature enabled"), + Err(GetOneError::Multiple) => panic!("Multiple mspm0xx/mspsxx Cargo features enabled"), + } + .strip_prefix("CARGO_FEATURE_") + .unwrap() + .to_ascii_lowercase() + .replace('_', "-"); + + eprintln!("chip: {chip_name}"); + + cfgs.enable_all(&get_chip_cfgs(&chip_name)); + for chip in ALL_CHIPS { + cfgs.declare_all(&get_chip_cfgs(&chip)); + } + + let mut singletons = get_singletons(&mut cfgs); + + time_driver(&mut singletons, &mut cfgs); + + let mut g = TokenStream::new(); + + g.extend(generate_singletons(&singletons)); + g.extend(generate_pincm_mapping()); + g.extend(generate_pin()); + g.extend(generate_timers()); + g.extend(generate_interrupts()); + g.extend(generate_peripheral_instances()); + g.extend(generate_pin_trait_impls()); + g.extend(generate_groups()); + + let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("_generated.rs").to_string_lossy().to_string(); + fs::write(&out_file, g.to_string()).unwrap(); + rustfmt(&out_file); +} + +fn get_chip_cfgs(chip_name: &str) -> Vec { + let mut cfgs = Vec::new(); + + // GPIO on C110x is special as it does not belong to an interrupt group. + if chip_name.starts_with("mspm0c110") || chip_name.starts_with("msps003f") { + cfgs.push("mspm0c110x".to_string()); + } + + // Family ranges (temporary until int groups are generated) + // + // TODO: Remove this once int group stuff is generated. + if chip_name.starts_with("mspm0g110") { + cfgs.push("mspm0g110x".to_string()); + } + + if chip_name.starts_with("mspm0g150") { + cfgs.push("mspm0g150x".to_string()); + } + + if chip_name.starts_with("mspm0g151") { + cfgs.push("mspm0g151x".to_string()); + } + + if chip_name.starts_with("mspm0g310") { + cfgs.push("mspm0g310x".to_string()); + } + + if chip_name.starts_with("mspm0g350") { + cfgs.push("mspm0g350x".to_string()); + } + + if chip_name.starts_with("mspm0g351") { + cfgs.push("mspm0g351x".to_string()); + } + + if chip_name.starts_with("mspm0l110") { + cfgs.push("mspm0l110x".to_string()); + } + + if chip_name.starts_with("mspm0l122") { + cfgs.push("mspm0l122x".to_string()); + } + + if chip_name.starts_with("mspm0l130") { + cfgs.push("mspm0l130x".to_string()); + } + + if chip_name.starts_with("mspm0l134") { + cfgs.push("mspm0l134x".to_string()); + } + + if chip_name.starts_with("mspm0l222") { + cfgs.push("mspm0l222x".to_string()); + } + + cfgs +} + +/// Interrupt groups use a weakly linked symbols and #[linkage = "extern_weak"] is nightly we need to +/// do some linker magic to create weak linkage. +fn interrupt_group_linker_magic() { + let mut file = String::new(); + + for group in METADATA.interrupt_groups { + for interrupt in group.interrupts.iter() { + let name = interrupt.name; + + writeln!(&mut file, "PROVIDE({name} = DefaultHandler);").unwrap(); + } + } + + let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("interrupt_group.x"); + fs::write(&out_file, file).unwrap(); +} + +fn generate_groups() -> TokenStream { + let group_vectors = METADATA.interrupt_groups.iter().map(|group| { + let vectors = group.interrupts.iter().map(|interrupt| { + let fn_name = Ident::new(interrupt.name, Span::call_site()); + + quote! { + pub(crate) fn #fn_name(); + } + }); + + quote! { #(#vectors)* } + }); + + let groups = METADATA.interrupt_groups.iter().map(|group| { + let interrupt_group_name = Ident::new(group.name, Span::call_site()); + let group_enum = Ident::new(&format!("Group{}", &group.name[5..]), Span::call_site()); + let group_number = Literal::u32_unsuffixed(group.number); + + let matches = group.interrupts.iter().map(|interrupt| { + let variant = Ident::new(&interrupt.name, Span::call_site()); + + quote! { + #group_enum::#variant => unsafe { group_vectors::#variant() }, + } + }); + + quote! { + #[cfg(feature = "rt")] + #[crate::pac::interrupt] + fn #interrupt_group_name() { + use crate::pac::#group_enum; + + let group = crate::pac::CPUSS.int_group(#group_number); + // MUST subtract by 1 since 0 is NO_INTR + let iidx = group.iidx().read().stat().to_bits() - 1; + + let Ok(group) = #group_enum::try_from(iidx as u8) else { + return; + }; + + match group { + #(#matches)* + } + } + } + }); + + quote! { + #(#groups)* + + #[cfg(feature = "rt")] + mod group_vectors { + extern "Rust" { + #(#group_vectors)* + } + } + } +} + +#[derive(Debug, Clone)] +struct Singleton { + name: String, + + cfg: Option, +} + +impl PartialEq for Singleton { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl Eq for Singleton {} + +impl PartialOrd for Singleton { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Singleton { + fn cmp(&self, other: &Self) -> Ordering { + self.name.cmp(&other.name) + } +} + +fn get_singletons(cfgs: &mut common::CfgSet) -> Vec { + let mut singletons = Vec::::new(); + + for peripheral in METADATA.peripherals { + // Some peripherals do not generate a singleton, but generate a singleton for each pin. + let skip_peripheral_singleton = match peripheral.kind { + "gpio" => { + // Also enable ports that are present. + match peripheral.name { + "GPIOB" => cfgs.enable("gpio_pb"), + "GPIOC" => cfgs.enable("gpio_pc"), + _ => (), + } + + true + } + + // Each channel gets a singleton, handled separately. + "dma" => true, + + // These peripherals do not exist as singletons, and have no signals but are managed + // by the HAL. + "iomux" | "cpuss" => true, + + _ => false, + }; + + if !skip_peripheral_singleton { + singletons.push(Singleton { + name: peripheral.name.to_string(), + cfg: None, + }); + } + + let mut signals = BTreeSet::new(); + + // Pick out each unique signal. There may be multiple instances of each signal due to + // iomux mappings. + for pin in peripheral.pins { + let signal = if peripheral.name.starts_with("GPIO") + || peripheral.name.starts_with("VREF") + || peripheral.name.starts_with("RTC") + { + pin.signal.to_string() + } else { + format!("{}_{}", peripheral.name, pin.signal) + }; + + // We need to rename some signals to become valid Rust identifiers. + let signal = make_valid_identifier(&signal); + signals.insert(signal); + } + + singletons.extend(signals); + } + + // DMA channels get their own singletons + for dma_channel in METADATA.dma_channels.iter() { + singletons.push(Singleton { + name: format!("DMA_CH{}", dma_channel.number), + cfg: None, + }); + } + + singletons.sort_by(|a, b| a.name.cmp(&b.name)); + singletons +} + +fn make_valid_identifier(s: &str) -> Singleton { + let name = s.replace('+', "_P").replace("-", "_N"); + + Singleton { name, cfg: None } +} + +fn generate_pincm_mapping() -> TokenStream { + let pincms = METADATA.pins.iter().map(|mapping| { + let port_letter = mapping.pin.strip_prefix("P").unwrap(); + let port_base = (port_letter.chars().next().unwrap() as u8 - b'A') * 32; + // This assumes all ports are single letter length. + // This is fine unless TI releases a part with 833+ GPIO pins. + let pin_number = mapping.pin[2..].parse::().unwrap(); + + let num = port_base + pin_number; + + // But subtract 1 since pincm indices start from 0, not 1. + let pincm = Literal::u8_unsuffixed(mapping.pincm - 1); + quote! { + #num => #pincm + } + }); + + quote! { + #[doc = "Get the mapping from GPIO pin port to IOMUX PINCM index. This is required since the mapping from IO to PINCM index is not consistent across parts."] + pub(crate) fn gpio_pincm(pin_port: u8) -> u8 { + match pin_port { + #(#pincms),*, + _ => unreachable!(), + } + } + } +} + +fn generate_pin() -> TokenStream { + let pin_impls = METADATA.pins.iter().map(|pin| { + let name = Ident::new(&pin.pin, Span::call_site()); + let port_letter = pin.pin.strip_prefix("P").unwrap(); + let port_letter = port_letter.chars().next().unwrap(); + let pin_number = Literal::u8_unsuffixed(pin.pin[2..].parse::().unwrap()); + + let port = Ident::new(&format!("Port{}", port_letter), Span::call_site()); + + // TODO: Feature gate pins that can be used as NRST + + quote! { + impl_pin!(#name, crate::gpio::Port::#port, #pin_number); + } + }); + + quote! { + #(#pin_impls)* + } +} + +fn time_driver(singletons: &mut Vec, cfgs: &mut CfgSet) { + // Timer features + for (timer, _) in TIMERS.iter() { + let name = timer.to_lowercase(); + cfgs.declare(&format!("time_driver_{}", name)); + } + + let time_driver = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) + .get_one() + { + Ok(x) => Some( + x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") + .unwrap() + .to_ascii_lowercase(), + ), + Err(GetOneError::None) => None, + Err(GetOneError::Multiple) => panic!("Multiple time-driver-xxx Cargo features enabled"), + }; + + // Verify the selected timer is available + let selected_timer = match time_driver.as_ref().map(|x| x.as_ref()) { + None => "", + Some("timg0") => "TIMG0", + Some("timg1") => "TIMG1", + Some("timg2") => "TIMG2", + Some("timg3") => "TIMG3", + Some("timg4") => "TIMG4", + Some("timg5") => "TIMG5", + Some("timg6") => "TIMG6", + Some("timg7") => "TIMG7", + Some("timg8") => "TIMG8", + Some("timg9") => "TIMG9", + Some("timg10") => "TIMG10", + Some("timg11") => "TIMG11", + Some("timg14") => "TIMG14", + Some("tima0") => "TIMA0", + Some("tima1") => "TIMA1", + Some("any") => { + // Order of timer candidates: + // 1. 16-bit, 2 channel + // 2. 16-bit, 2 channel with shadow registers + // 3. 16-bit, 4 channel + // 4. 16-bit with QEI + // 5. Advanced timers + // + // TODO: Select RTC first if available + // TODO: 32-bit timers are not considered yet + [ + // 16-bit, 2 channel + "TIMG0", "TIMG1", "TIMG2", "TIMG3", // 16-bit, 2 channel with shadow registers + "TIMG4", "TIMG5", "TIMG6", "TIMG7", // 16-bit, 4 channel + "TIMG14", // 16-bit with QEI + "TIMG8", "TIMG9", "TIMG10", "TIMG11", // Advanced timers + "TIMA0", "TIMA1", + ] + .iter() + .find(|tim| singletons.iter().any(|s| s.name == **tim)) + .expect("Could not find any timer") + } + _ => panic!("unknown time_driver {:?}", time_driver), + }; + + if !selected_timer.is_empty() { + cfgs.enable(format!("time_driver_{}", selected_timer.to_lowercase())); + } + + // Apply cfgs to each timer and it's pins + for singleton in singletons.iter_mut() { + if singleton.name.starts_with("TIM") { + // Remove suffixes for pin singletons. + let name = if singleton.name.contains("_CCP") { + singleton.name.split_once("_CCP").unwrap().0 + } else if singleton.name.contains("_FAULT") { + singleton.name.split_once("_FAULT").unwrap().0 + } else if singleton.name.contains("_IDX") { + singleton.name.split_once("_IDX").unwrap().0 + } else { + &singleton.name + }; + + let feature = format!("time-driver-{}", name.to_lowercase()); + + if singleton.name.contains(selected_timer) { + singleton.cfg = Some(quote! { #[cfg(not(all(feature = "time-driver-any", feature = #feature)))] }); + } else { + singleton.cfg = Some(quote! { #[cfg(not(feature = #feature))] }); + } + } + } +} + +fn generate_singletons(singletons: &[Singleton]) -> TokenStream { + let singletons = singletons + .iter() + .map(|s| { + let cfg = s.cfg.clone().unwrap_or_default(); + + let ident = format_ident!("{}", s.name); + + quote! { + #cfg + #ident + } + }) + .collect::>(); + + quote! { + embassy_hal_internal::peripherals_definition!(#(#singletons),*); + embassy_hal_internal::peripherals_struct!(#(#singletons),*); + } +} + +fn generate_timers() -> TokenStream { + // Generate timers + let timer_impls = METADATA + .peripherals + .iter() + .filter(|p| p.name.starts_with("TIM")) + .map(|peripheral| { + let name = Ident::new(&peripheral.name, Span::call_site()); + let timers = &*TIMERS; + + let timer = timers.get(peripheral.name).expect("Timer does not exist"); + assert!(timer.bits == 16 || timer.bits == 32); + let bits = if timer.bits == 16 { + quote! { Bits16 } + } else { + quote! { Bits32 } + }; + + quote! { + impl_timer!(#name, #bits); + } + }); + + quote! { + #(#timer_impls)* + } +} + +fn generate_interrupts() -> TokenStream { + // Generate interrupt module + let interrupts: Vec = METADATA + .interrupts + .iter() + .map(|interrupt| Ident::new(interrupt.name, Span::call_site())) + .collect(); + + let group_interrupt_enables = METADATA + .interrupts + .iter() + .filter(|interrupt| interrupt.name.contains("GROUP")) + .map(|interrupt| { + let name = Ident::new(interrupt.name, Span::call_site()); + + quote! { + crate::interrupt::typelevel::#name::enable(); + } + }); + + // Generate interrupt enables for groups + quote! { + embassy_hal_internal::interrupt_mod! { + #(#interrupts),* + } + + pub fn enable_group_interrupts(_cs: critical_section::CriticalSection) { + use crate::interrupt::typelevel::Interrupt; + + unsafe { + #(#group_interrupt_enables)* + } + } + } +} + +fn generate_peripheral_instances() -> TokenStream { + let mut impls = Vec::::new(); + + for peripheral in METADATA.peripherals { + let peri = format_ident!("{}", peripheral.name); + + // Will be filled in when uart implementation is finished + let _ = peri; + let tokens = match peripheral.kind { + "uart" => Some(quote! { impl_uart_instance!(#peri); }), + _ => None, + }; + + if let Some(tokens) = tokens { + impls.push(tokens); + } + } + + quote! { + #(#impls)* + } +} + +fn generate_pin_trait_impls() -> TokenStream { + let mut impls = Vec::::new(); + + for peripheral in METADATA.peripherals { + for pin in peripheral.pins { + let key = (peripheral.kind, pin.signal); + + let pin_name = format_ident!("{}", pin.pin); + let peri = format_ident!("{}", peripheral.name); + let pf = pin.pf; + + // Will be filled in when uart implementation is finished + let _ = pin_name; + let _ = peri; + let _ = pf; + + let tokens = match key { + ("uart", "TX") => Some(quote! { impl_uart_tx_pin!(#peri, #pin_name, #pf); }), + ("uart", "RX") => Some(quote! { impl_uart_rx_pin!(#peri, #pin_name, #pf); }), + ("uart", "CTS") => Some(quote! { impl_uart_cts_pin!(#peri, #pin_name, #pf); }), + ("uart", "RTS") => Some(quote! { impl_uart_rts_pin!(#peri, #pin_name, #pf); }), + _ => None, + }; + + if let Some(tokens) = tokens { + impls.push(tokens); + } + } + } + + quote! { + #(#impls)* + } +} + +/// rustfmt a given path. +/// Failures are logged to stderr and ignored. +fn rustfmt(path: impl AsRef) { + let path = path.as_ref(); + match Command::new("rustfmt").args([path]).output() { + Err(e) => { + eprintln!("failed to exec rustfmt {:?}: {:?}", path, e); + } + Ok(out) => { + if !out.status.success() { + eprintln!("rustfmt {:?} failed:", path); + eprintln!("=== STDOUT:"); + std::io::stderr().write_all(&out.stdout).unwrap(); + eprintln!("=== STDERR:"); + std::io::stderr().write_all(&out.stderr).unwrap(); + } + } + } +} + +#[allow(dead_code)] +struct TimerDesc { + bits: u8, + /// Is there an 8-bit prescaler + prescaler: bool, + /// Is there a repeat counter + repeat_counter: bool, + ccp_channels_internal: u8, + ccp_channels_external: u8, + external_pwm_channels: u8, + phase_load: bool, + shadow_load: bool, + shadow_ccs: bool, + deadband: bool, + fault_handler: bool, + qei_hall: bool, +} + +/// Description of all timer instances. +const TIMERS: LazyLock> = LazyLock::new(|| { + let mut map = HashMap::new(); + map.insert( + "TIMG0".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG1".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG2".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG3".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG4".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: true, + shadow_ccs: true, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG5".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: true, + shadow_ccs: true, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG6".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: true, + shadow_ccs: true, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG7".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: true, + shadow_ccs: true, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG8".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: true, + }, + ); + + map.insert( + "TIMG9".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: true, + }, + ); + + map.insert( + "TIMG10".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: true, + }, + ); + + map.insert( + "TIMG11".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: true, + }, + ); + + map.insert( + "TIMG12".into(), + TimerDesc { + bits: 32, + prescaler: false, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: true, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG13".into(), + TimerDesc { + bits: 32, + prescaler: false, + repeat_counter: false, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 2, + phase_load: false, + shadow_load: false, + shadow_ccs: true, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMG14".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: false, + ccp_channels_internal: 4, + ccp_channels_external: 4, + external_pwm_channels: 4, + phase_load: false, + shadow_load: false, + shadow_ccs: false, + deadband: false, + fault_handler: false, + qei_hall: false, + }, + ); + + map.insert( + "TIMA0".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: true, + ccp_channels_internal: 4, + ccp_channels_external: 2, + external_pwm_channels: 8, + phase_load: true, + shadow_load: true, + shadow_ccs: true, + deadband: true, + fault_handler: true, + qei_hall: false, + }, + ); + + map.insert( + "TIMA1".into(), + TimerDesc { + bits: 16, + prescaler: true, + repeat_counter: true, + ccp_channels_internal: 2, + ccp_channels_external: 2, + external_pwm_channels: 4, + phase_load: true, + shadow_load: true, + shadow_ccs: true, + deadband: true, + fault_handler: true, + qei_hall: false, + }, + ); + + map +}); + +enum GetOneError { + None, + Multiple, +} + +trait IteratorExt: Iterator { + fn get_one(self) -> Result; +} + +impl IteratorExt for T { + fn get_one(mut self) -> Result { + match self.next() { + None => Err(GetOneError::None), + Some(res) => match self.next() { + Some(_) => Err(GetOneError::Multiple), + None => Ok(res), + }, + } + } +} diff --git a/embassy-mspm0/build_common.rs b/embassy-mspm0/build_common.rs new file mode 100644 index 000000000..4f24e6d37 --- /dev/null +++ b/embassy-mspm0/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy-mspm0/src/fmt.rs b/embassy-mspm0/src/fmt.rs new file mode 100644 index 000000000..8ca61bc39 --- /dev/null +++ b/embassy-mspm0/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-mspm0/src/gpio.rs b/embassy-mspm0/src/gpio.rs new file mode 100644 index 000000000..738d51928 --- /dev/null +++ b/embassy-mspm0/src/gpio.rs @@ -0,0 +1,1154 @@ +#![macro_use] + +use core::convert::Infallible; +use core::future::Future; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::pac::gpio::vals::*; +use crate::pac::gpio::{self}; +#[cfg(all(feature = "rt", any(mspm0c110x, mspm0l110x)))] +use crate::pac::interrupt; +use crate::pac::{self}; + +/// Represents a digital input or output level. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + /// Logical low. + Low, + /// Logical high. + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +/// Represents a pull setting for an input. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Pull { + /// No pull. + None, + /// Internal pull-up resistor. + Up, + /// Internal pull-down resistor. + Down, +} + +/// A GPIO bank with up to 32 pins. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Port { + /// Port A. + PortA = 0, + + /// Port B. + #[cfg(gpio_pb)] + PortB = 1, + + /// Port C. + #[cfg(gpio_pc)] + PortC = 2, +} + +/// GPIO flexible pin. +/// +/// 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> { + pin: Peri<'d, AnyPin>, +} + +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: Peri<'d, impl Pin>) -> Self { + // Pin will be in disconnected state. + Self { pin: pin.into() } + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); + + pincm.modify(|w| { + w.set_pipd(matches!(pull, Pull::Down)); + w.set_pipu(matches!(pull, Pull::Up)); + }); + } + + /// Put the pin into input mode. + /// + /// The pull setting is left unchanged. + #[inline] + pub fn set_as_input(&mut self) { + let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); + + pincm.modify(|w| { + w.set_pf(GPIO_PF); + w.set_hiz1(false); + w.set_pc(true); + w.set_inena(true); + }); + + self.pin.block().doeclr31_0().write(|w| { + w.set_dio(self.pin.bit_index(), true); + }); + } + + /// Put the pin into output mode. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + #[inline] + pub fn set_as_output(&mut self) { + let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); + + pincm.modify(|w| { + w.set_pf(GPIO_PF); + w.set_hiz1(false); + w.set_pc(true); + w.set_inena(false); + }); + + self.pin.block().doeset31_0().write(|w| { + w.set_dio(self.pin.bit_index(), true); + }); + } + + /// Put the pin into input + open-drain output mode. + /// + /// The hardware will drive the line low if you set it to low, and will leave it floating if you set + /// it to high, in which case you can read the input to figure out whether another device + /// is driving the line low. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + /// + /// The internal weak pull-up and pull-down resistors will be disabled. + #[inline] + pub fn set_as_input_output(&mut self) { + let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); + + pincm.modify(|w| { + w.set_pf(GPIO_PF); + w.set_hiz1(true); + w.set_pc(true); + w.set_inena(false); + }); + + self.set_pull(Pull::None); + } + + /// Set the pin as "disconnected", ie doing nothing and consuming the lowest + /// amount of power possible. + /// + /// This is currently the same as [`Self::set_as_analog()`] but is semantically different + /// really. Drivers should `set_as_disconnected()` pins when dropped. + /// + /// Note that this also disables the internal weak pull-up and pull-down resistors. + #[inline] + pub fn set_as_disconnected(&mut self) { + let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); + + pincm.modify(|w| { + w.set_pf(DISCONNECT_PF); + w.set_hiz1(false); + w.set_pc(false); + w.set_inena(false); + }); + + self.set_pull(Pull::None); + self.set_inversion(false); + } + + /// Configure the logic inversion of this pin. + /// + /// Logic inversion applies to both the input and output path of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); + + pincm.modify(|w| { + w.set_inv(invert); + }); + } + + // TODO: drive strength, hysteresis, wakeup enable, wakeup compare + + /// Put the pin into the PF mode, unchecked. + /// + /// This puts the pin into the PF mode, with the request number. This is completely unchecked, + /// it can attach the pin to literally any peripheral, so use with care. In addition the pin + /// peripheral is connected in the iomux. + /// + /// The peripheral attached to the pin depends on the part in use. Consult the datasheet + /// or technical reference manual for additional details. + #[inline] + pub fn set_pf_unchecked(&mut self, pf: u8) { + // Per SLAU893 and SLAU846B, PF is only 6 bits + assert_eq!(pf & 0xC0, 0, "PF is out of range"); + + let pincm = pac::IOMUX.pincm(self.pin.pin_cm() as usize); + + pincm.modify(|w| { + w.set_pf(pf); + // If the PF is manually set, connect the pin + w.set_pc(true); + }); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.is_low() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.block().din31_0().read().dio(self.pin.bit_index()) + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.block().doutset31_0().write(|w| { + w.set_dio(self.pin.bit_index() as usize, true); + }); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.block().doutclr31_0().write(|w| { + w.set_dio(self.pin.bit_index(), true); + }); + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.block().douttgl31_0().write(|w| { + w.set_dio(self.pin.bit_index(), true); + }) + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.set_low(), + Level::High => self.set_high(), + } + } + + /// Get the current pin input level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_high().into() + } + + /// Is the output level high? + #[inline] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + /// Is the output level low? + #[inline] + pub fn is_set_low(&self) -> bool { + (self.pin.block().dout31_0().read().0 & self.pin.bit_index() as u32) == 0 + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + if self.is_high() { + return; + } + + self.wait_for_rising_edge().await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + if self.is_low() { + return; + } + + self.wait_for_falling_edge().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(self.pin.reborrow(), Polarity::RISE).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(self.pin.reborrow(), Polarity::FALL).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(self.pin.reborrow(), Polarity::RISE_FALL).await + } +} + +impl<'d> Drop for Flex<'d> { + #[inline] + fn drop(&mut self) { + self.set_as_disconnected(); + } +} + +/// GPIO input driver. +pub struct Input<'d> { + pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + pin.set_pull(pull); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Configure the logic inversion of this pin. + /// + /// Logic inversion applies to the input path of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + self.pin.set_inversion(invert) + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await + } +} + +/// GPIO output driver. +/// +/// 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> { + pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Level] configuration. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_output(); + pin.set_level(initial_output); + Self { pin } + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle(); + } + + /// Configure the logic inversion of this pin. + /// + /// Logic inversion applies to the input path of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + self.pin.set_inversion(invert) + } +} + +/// GPIO output open-drain driver. +/// +/// 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> { + pin: Flex<'d>, +} + +impl<'d> OutputOpenDrain<'d> { + /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level]. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_level(initial_output); + pin.set_as_input_output(); + Self { pin } + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.pin.is_low() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Get the current pin input level. + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high(); + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low(); + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level); + } + + /// Get whether the output level is set to high. + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Get whether the output level is set to low. + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// Get the current output level. + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle() + } + + /// Configure the logic inversion of this pin. + /// + /// Logic inversion applies to the input path of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + self.pin.set_inversion(invert) + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await + } +} + +/// Type-erased GPIO pin +pub struct AnyPin { + pub(crate) pin_port: u8, +} + +impl AnyPin { + /// Create an [AnyPin] for a specific pin. + /// + /// # Safety + /// - `pin_port` should not in use by another driver. + #[inline] + pub unsafe fn steal(pin_port: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_port }) + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + #[inline] + fn pin_port(&self) -> u8 { + self.pin_port + } +} + +/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an [AnyPin]. +#[allow(private_bounds)] +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { + /// The index of this pin in PINCM (pin control management) registers. + #[inline] + fn pin_cm(&self) -> u8 { + self._pin_cm() + } +} + +impl<'d> embedded_hal::digital::ErrorType for Flex<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal::digital::InputPin for Flex<'d> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal::digital::OutputPin for Flex<'d> { + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } +} + +impl<'d> embedded_hal::digital::StatefulOutputPin for Flex<'d> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +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(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl<'d> embedded_hal::digital::ErrorType for Input<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal::digital::InputPin for Input<'d> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +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(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl<'d> embedded_hal::digital::ErrorType for Output<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal::digital::OutputPin for Output<'d> { + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } +} + +impl<'d> embedded_hal::digital::StatefulOutputPin for Output<'d> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal::digital::ErrorType for OutputOpenDrain<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal::digital::InputPin for OutputOpenDrain<'d> { + #[inline] + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + #[inline] + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal::digital::OutputPin for OutputOpenDrain<'d> { + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } +} + +impl<'d> embedded_hal::digital::StatefulOutputPin for OutputOpenDrain<'d> { + #[inline] + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + #[inline] + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +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(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +#[derive(Copy, Clone)] +pub struct PfType { + pull: Pull, + input: bool, + invert: bool, +} + +impl PfType { + pub const fn input(pull: Pull, invert: bool) -> Self { + Self { + pull, + input: true, + invert, + } + } + + pub const fn output(pull: Pull, invert: bool) -> Self { + Self { + pull, + input: false, + invert, + } + } +} + +/// The pin function to disconnect peripherals from the pin. +/// +/// This is also the pin function used to connect to analog peripherals, such as an ADC. +const DISCONNECT_PF: u8 = 0; + +/// The pin function for the GPIO peripheral. +/// +/// This is fixed to `1` for every part. +const GPIO_PF: u8 = 1; + +macro_rules! impl_pin { + ($name: ident, $port: expr, $pin_num: expr) => { + impl crate::gpio::Pin for crate::peripherals::$name {} + impl crate::gpio::SealedPin for crate::peripherals::$name { + #[inline] + fn pin_port(&self) -> u8 { + ($port as u8) * 32 + $pin_num + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: crate::peripherals::$name) -> Self { + Self { + pin_port: crate::gpio::SealedPin::pin_port(&val), + } + } + } + }; +} + +// TODO: Possible micro-op for C110X, not every pin is instantiated even on the 20 pin parts. +// This would mean cfg guarding to just cfg guarding every pin instance. +static PORTA_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio_pb)] +static PORTB_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; +#[cfg(gpio_pc)] +static PORTC_WAKERS: [AtomicWaker; 32] = [const { AtomicWaker::new() }; 32]; + +pub(crate) trait SealedPin { + fn pin_port(&self) -> u8; + + fn port(&self) -> Port { + match self.pin_port() / 32 { + 0 => Port::PortA, + #[cfg(gpio_pb)] + 1 => Port::PortB, + #[cfg(gpio_pc)] + 2 => Port::PortC, + _ => unreachable!(), + } + } + + fn waker(&self) -> &AtomicWaker { + match self.port() { + Port::PortA => &PORTA_WAKERS[self.bit_index()], + #[cfg(gpio_pb)] + Port::PortB => &PORTB_WAKERS[self.bit_index()], + #[cfg(gpio_pc)] + Port::PortC => &PORTC_WAKERS[self.bit_index()], + } + } + + fn _pin_cm(&self) -> u8 { + // Some parts like the MSPM0L222x have pincm mappings all over the place. + crate::gpio_pincm(self.pin_port()) + } + + fn bit_index(&self) -> usize { + (self.pin_port() % 32) as usize + } + + #[inline] + fn set_as_analog(&self) { + let pincm = pac::IOMUX.pincm(self._pin_cm() as usize); + + pincm.modify(|w| { + w.set_pf(DISCONNECT_PF); + w.set_pipu(false); + w.set_pipd(false); + }); + } + + fn update_pf(&self, ty: PfType) { + let pincm = pac::IOMUX.pincm(self._pin_cm() as usize); + let pf = pincm.read().pf(); + + set_pf(self._pin_cm() as usize, pf, ty); + } + + fn set_as_pf(&self, pf: u8, ty: PfType) { + set_pf(self._pin_cm() as usize, pf, ty) + } + + /// Set the pin as "disconnected", ie doing nothing and consuming the lowest + /// amount of power possible. + /// + /// This is currently the same as [`Self::set_as_analog()`] but is semantically different + /// really. Drivers should `set_as_disconnected()` pins when dropped. + /// + /// Note that this also disables the internal weak pull-up and pull-down resistors. + #[inline] + fn set_as_disconnected(&self) { + self.set_as_analog(); + } + + #[inline] + fn block(&self) -> gpio::Gpio { + match self.pin_port() / 32 { + 0 => pac::GPIOA, + #[cfg(gpio_pb)] + 1 => pac::GPIOB, + #[cfg(gpio_pc)] + 2 => pac::GPIOC, + _ => unreachable!(), + } + } +} + +#[inline(never)] +fn set_pf(pincm: usize, pf: u8, ty: PfType) { + pac::IOMUX.pincm(pincm).modify(|w| { + w.set_pf(pf); + w.set_pc(true); + w.set_pipu(ty.pull == Pull::Up); + w.set_pipd(ty.pull == Pull::Down); + w.set_inena(ty.input); + w.set_inv(ty.invert); + }); +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + pin: Peri<'d, AnyPin>, +} + +impl<'d> InputFuture<'d> { + fn new(pin: Peri<'d, AnyPin>, polarity: Polarity) -> Self { + let block = pin.block(); + + // Before clearing any previous edge events, we must disable events. + // + // If we don't do this, it is possible that after we clear the interrupt, the current event + // the hardware is listening for may not be the same event we will configure. This may result + // in RIS being set. Then when interrupts are unmasked and RIS is set, we may get the wrong event + // causing an interrupt. + // + // Selecting which polarity events happen is a RMW operation. + critical_section::with(|_cs| { + if pin.bit_index() >= 16 { + block.polarity31_16().modify(|w| { + w.set_dio(pin.bit_index() - 16, Polarity::DISABLE); + }); + } else { + block.polarity15_0().modify(|w| { + w.set_dio(pin.bit_index(), Polarity::DISABLE); + }); + }; + }); + + // First clear the bit for this event. Otherwise previous edge events may be recorded. + block.cpu_int().iclr().write(|w| { + w.set_dio(pin.bit_index(), true); + }); + + // Selecting which polarity events happen is a RMW operation. + critical_section::with(|_cs| { + // Tell the hardware which pin event we want to receive. + if pin.bit_index() >= 16 { + block.polarity31_16().modify(|w| { + w.set_dio(pin.bit_index() - 16, polarity); + }); + } else { + block.polarity15_0().modify(|w| { + w.set_dio(pin.bit_index(), polarity); + }); + }; + }); + + Self { pin } + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + let waker = self.pin.waker(); + waker.register(cx.waker()); + + // The interrupt handler will mask the interrupt if the event has occurred. + if self.pin.block().cpu_int().ris().read().dio(self.pin.bit_index()) { + return Poll::Ready(()); + } + + // Unmasking the interrupt is a RMW operation. + // + // Guard with a critical section in case two different threads try to unmask at the same time. + critical_section::with(|_cs| { + self.pin.block().cpu_int().imask().modify(|w| { + w.set_dio(self.pin.bit_index(), true); + }); + }); + + Poll::Pending + } +} + +pub(crate) fn init(gpio: gpio::Gpio) { + gpio.gprcm().rstctl().write(|w| { + w.set_resetstkyclr(true); + w.set_resetassert(true); + w.set_key(ResetKey::KEY); + }); + + gpio.gprcm().pwren().write(|w| { + w.set_enable(true); + w.set_key(PwrenKey::KEY); + }); + + gpio.evt_mode().modify(|w| { + // The CPU will clear it's own interrupts + w.set_cpu_cfg(EvtCfg::SOFTWARE); + }); +} + +#[cfg(feature = "rt")] +fn irq_handler(gpio: gpio::Gpio, wakers: &[AtomicWaker; 32]) { + // Only consider pins which have interrupts unmasked. + let bits = gpio.cpu_int().mis().read().0; + + for i in BitIter(bits) { + wakers[i as usize].wake(); + + // Notify the future that an edge event has occurred by masking the interrupt for this pin. + gpio.cpu_int().imask().modify(|w| { + w.set_dio(i as usize, false); + }); + } +} + +struct BitIter(u32); + +impl Iterator for BitIter { + type Item = u32; + + fn next(&mut self) -> Option { + match self.0.trailing_zeros() { + 32 => None, + b => { + self.0 &= !(1 << b); + Some(b) + } + } + } +} + +// C110x and L110x have a dedicated interrupts just for GPIOA. +// +// These chips do not have a GROUP1 interrupt. +#[cfg(all(feature = "rt", any(mspm0c110x, mspm0l110x)))] +#[interrupt] +fn GPIOA() { + irq_handler(pac::GPIOA, &PORTA_WAKERS); +} + +// These symbols are weakly defined as DefaultHandler and are called by the interrupt group implementation. +// +// Defining these as no_mangle is required so that the linker will pick these over the default handler. + +#[cfg(all(feature = "rt", not(any(mspm0c110x, mspm0l110x))))] +#[no_mangle] +#[allow(non_snake_case)] +fn GPIOA() { + irq_handler(pac::GPIOA, &PORTA_WAKERS); +} + +#[cfg(all(feature = "rt", gpio_pb))] +#[no_mangle] +#[allow(non_snake_case)] +fn GPIOB() { + irq_handler(pac::GPIOB, &PORTB_WAKERS); +} + +#[cfg(all(feature = "rt", gpio_pc))] +#[allow(non_snake_case)] +#[no_mangle] +fn GPIOC() { + irq_handler(pac::GPIOC, &PORTC_WAKERS); +} diff --git a/embassy-mspm0/src/lib.rs b/embassy-mspm0/src/lib.rs new file mode 100644 index 000000000..7ff60e946 --- /dev/null +++ b/embassy-mspm0/src/lib.rs @@ -0,0 +1,120 @@ +#![no_std] +// Doc feature labels can be tested locally by running RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc +#![cfg_attr(docsrs, feature(doc_auto_cfg, doc_cfg_hide), doc(cfg_hide(doc, docsrs)))] +#![cfg_attr( + docsrs, + doc = "

You might want to browse the `embassy-mspm0` documentation on the Embassy website instead.

The documentation here on `docs.rs` is built for a single chip only, while on the Embassy website you can pick your exact chip from the top menu. Available peripherals and their APIs change depending on the chip.

\n\n" +)] +#![doc = include_str!("../README.md")] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +// This must be declared early as well for +mod macros; + +pub mod gpio; +pub mod timer; +pub mod uart; + +/// Operating modes for peripherals. +pub mod mode { + trait SealedMode {} + + /// Operating mode for a peripheral. + #[allow(private_bounds)] + pub trait Mode: SealedMode {} + + /// Blocking mode. + pub struct Blocking; + impl SealedMode for Blocking {} + impl Mode for Blocking {} + + /// Async mode. + pub struct Async; + impl SealedMode for Async {} + impl Mode for Async {} +} + +#[cfg(feature = "_time-driver")] +mod time_driver; + +pub(crate) mod _generated { + #![allow(dead_code)] + #![allow(unused_imports)] + #![allow(non_snake_case)] + #![allow(missing_docs)] + + include!(concat!(env!("OUT_DIR"), "/_generated.rs")); +} + +// Reexports +pub(crate) use _generated::gpio_pincm; +pub use _generated::{peripherals, Peripherals}; +pub use embassy_hal_internal::Peri; +#[cfg(feature = "unstable-pac")] +pub use mspm0_metapac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use mspm0_metapac as pac; + +pub use crate::_generated::interrupt; + +/// `embassy-mspm0` global configuration. +#[non_exhaustive] +#[derive(Clone, Copy)] +pub struct Config { + // TODO +} + +impl Default for Config { + fn default() -> Self { + Self { + // TODO + } + } +} + +pub fn init(_config: Config) -> Peripherals { + critical_section::with(|cs| { + let peripherals = Peripherals::take_with_cs(cs); + + // TODO: Further clock configuration + + pac::SYSCTL.mclkcfg().modify(|w| { + // Enable MFCLK + w.set_usemftick(true); + // MDIV must be disabled if MFCLK is enabled. + w.set_mdiv(0); + }); + + // Enable MFCLK for peripheral use + // + // TODO: Optional? + pac::SYSCTL.genclken().modify(|w| { + w.set_mfpclken(true); + }); + + pac::SYSCTL.borthreshold().modify(|w| { + w.set_level(0); + }); + + gpio::init(pac::GPIOA); + #[cfg(gpio_pb)] + gpio::init(pac::GPIOB); + #[cfg(gpio_pc)] + gpio::init(pac::GPIOC); + + _generated::enable_group_interrupts(cs); + + #[cfg(mspm0c110x)] + unsafe { + use crate::_generated::interrupt::typelevel::Interrupt; + crate::interrupt::typelevel::GPIOA::enable(); + } + + #[cfg(feature = "_time-driver")] + time_driver::init(cs); + + peripherals + }) +} diff --git a/embassy-mspm0/src/macros.rs b/embassy-mspm0/src/macros.rs new file mode 100644 index 000000000..5355e7d59 --- /dev/null +++ b/embassy-mspm0/src/macros.rs @@ -0,0 +1,9 @@ +#![macro_use] + +macro_rules! new_pin { + ($name: ident, $pf_type: expr) => {{ + let pin = $name; + pin.set_as_pf(pin.pf_num(), $pf_type); + Some(pin.into()) + }}; +} diff --git a/embassy-mspm0/src/time_driver.rs b/embassy-mspm0/src/time_driver.rs new file mode 100644 index 000000000..e80e89e55 --- /dev/null +++ b/embassy-mspm0/src/time_driver.rs @@ -0,0 +1,426 @@ +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; +use core::task::Waker; + +use critical_section::{CriticalSection, Mutex}; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; +use mspm0_metapac::interrupt; +use mspm0_metapac::tim::vals::{Cm, Cvae, CxC, EvtCfg, PwrenKey, Ratio, Repeat, ResetKey}; +use mspm0_metapac::tim::{Counterregs16, Tim}; + +use crate::peripherals; +use crate::timer::SealedTimer; + +#[cfg(any(time_driver_timg12, time_driver_timg13))] +compile_error!("TIMG12 and TIMG13 are not supported by the time driver yet"); + +// Currently TIMG12 and TIMG13 are excluded because those are 32-bit timers. +#[cfg(time_driver_timg0)] +type T = peripherals::TIMG0; +#[cfg(time_driver_timg1)] +type T = peripherals::TIMG1; +#[cfg(time_driver_timg2)] +type T = peripherals::TIMG2; +#[cfg(time_driver_timg3)] +type T = peripherals::TIMG3; +#[cfg(time_driver_timg4)] +type T = peripherals::TIMG4; +#[cfg(time_driver_timg5)] +type T = peripherals::TIMG5; +#[cfg(time_driver_timg6)] +type T = peripherals::TIMG6; +#[cfg(time_driver_timg7)] +type T = peripherals::TIMG7; +#[cfg(time_driver_timg8)] +type T = peripherals::TIMG8; +#[cfg(time_driver_timg9)] +type T = peripherals::TIMG9; +#[cfg(time_driver_timg10)] +type T = peripherals::TIMG10; +#[cfg(time_driver_timg11)] +type T = peripherals::TIMG11; +#[cfg(time_driver_timg14)] +type T = peripherals::TIMG14; +#[cfg(time_driver_tima0)] +type T = peripherals::TIMA0; +#[cfg(time_driver_tima1)] +type T = peripherals::TIMA1; + +// TODO: RTC + +fn regs() -> Tim { + unsafe { Tim::from_ptr(T::regs()) } +} + +fn regs_counter(tim: Tim) -> Counterregs16 { + unsafe { Counterregs16::from_ptr(tim.counterregs(0).as_ptr()) } +} + +/// Clock timekeeping works with something we call "periods", which are time intervals +/// of 2^15 ticks. The Clock counter value is 16 bits, so one "overflow cycle" is 2 periods. +fn calc_now(period: u32, counter: u16) -> u64 { + ((period as u64) << 15) + ((counter as u32 ^ ((period & 1) << 15)) as u64) +} + +/// The TIMx driver uses one of the `TIMG` or `TIMA` timer instances to implement a timer with a 32.768 kHz +/// tick rate. (TODO: Allow setting the tick rate) +/// +/// This driver defines a period to be 2^15 ticks. 16-bit timers of course count to 2^16 ticks. +/// +/// To generate a period every 2^15 ticks, the CC0 value is set to 2^15 and the load value set to 2^16. +/// Incrementing the period on a CCU0 and load results in the a period of 2^15 ticks. +/// +/// For a specific timestamp, load the lower 16 bits into the CC1 value. When the period where the timestamp +/// should be enabled is reached, then the CCU1 (CC1 up) interrupt runs to actually wake the timer. +/// +/// TODO: Compensate for per part variance. This can supposedly be done with the FCC system. +/// TODO: Allow using 32-bit timers (TIMG12 and TIMG13). +struct TimxDriver { + /// Number of 2^15 periods elapsed since boot. + period: AtomicU32, + /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. + alarm: Mutex>, + queue: Mutex>, +} + +impl TimxDriver { + #[inline(never)] + fn init(&'static self, _cs: CriticalSection) { + // Clock config + // TODO: Configurable tick rate up to 4 MHz (32 kHz for now) + let regs = regs(); + + // Reset timer + regs.gprcm(0).rstctl().write(|w| { + w.set_resetassert(true); + w.set_key(ResetKey::KEY); + w.set_resetstkyclr(true); + }); + + // Power up timer + regs.gprcm(0).pwren().write(|w| { + w.set_enable(true); + w.set_key(PwrenKey::KEY); + }); + + // Following the instructions according to SLAU847D 23.2.1: TIMCLK Configuration + + // 1. Select TIMCLK source + regs.clksel().modify(|w| { + // Use LFCLK for a 32.768kHz tick rate + w.set_lfclk_sel(true); + // TODO: Allow MFCLK for configurable tick rate up to 4 MHz + // w.set_mfclk_sel(ClkSel::ENABLE); + }); + + // 2. Divide by TIMCLK, we don't need to divide further for the 32kHz tick rate + regs.clkdiv().modify(|w| { + w.set_ratio(Ratio::DIV_BY_1); + }); + + // 3. To be generic across timer instances, we do not use the prescaler. + // TODO: mspm0-sdk always sets this, regardless of timer width? + regs.commonregs(0).cps().modify(|w| { + w.set_pcnt(0); + }); + + regs.pdbgctl().modify(|w| { + w.set_free(true); + }); + + // 4. Enable the TIMCLK. + regs.commonregs(0).cclkctl().modify(|w| { + w.set_clken(true); + }); + + regs.counterregs(0).ctrctl().modify(|w| { + // allow counting during debug + w.set_repeat(Repeat::REPEAT_3); + w.set_cvae(Cvae::ZEROVAL); + w.set_cm(Cm::UP); + + // Must explicitly set CZC, CAC and CLC to 0 in order for all the timers to count. + // + // The reset value of these registers is 0x07, which is a reserved value. + // + // Looking at a bit representation of the reset value, this appears to be an AND + // of 2-input QEI mode and CCCTL_3 ACOND. Given that TIMG14 and TIMA0 have no QEI + // and 4 capture and compare channels, this works by accident for those timer units. + w.set_czc(CxC::CCTL0); + w.set_cac(CxC::CCTL0); + w.set_clc(CxC::CCTL0); + }); + + // Setup the period + let ctr = regs_counter(regs); + + // Middle + ctr.cc(0).modify(|w| { + w.set_ccval(0x7FFF); + }); + + ctr.load().modify(|w| { + w.set_ld(u16::MAX); + }); + + // Enable the period interrupts + // + // This does not appear to ever be set for CPU_INT in the TI SDK and is not technically needed. + regs.evt_mode().modify(|w| { + w.set_evt_cfg(0, EvtCfg::SOFTWARE); + }); + + regs.int_event(0).imask().modify(|w| { + w.set_l(true); + w.set_ccu0(true); + }); + + unsafe { T::enable_interrupt() }; + + // Allow the counter to start counting. + regs.counterregs(0).ctrctl().modify(|w| { + w.set_en(true); + }); + } + + #[inline(never)] + fn next_period(&self) { + let r = regs(); + + // We only modify the period from the timer interrupt, so we know this can't race. + let period = self.period.load(Ordering::Relaxed) + 1; + self.period.store(period, Ordering::Relaxed); + let t = (period as u64) << 15; + + critical_section::with(move |cs| { + r.int_event(0).imask().modify(move |w| { + let alarm = self.alarm.borrow(cs); + let at = alarm.get(); + + if at < t + 0xC000 { + // just enable it. `set_alarm` has already set the correct CC1 val. + w.set_ccu1(true); + } + }) + }); + } + + #[inline(never)] + fn on_interrupt(&self) { + let r = regs(); + + critical_section::with(|cs| { + let mis = r.int_event(0).mis().read(); + + // Advance to next period if overflowed + if mis.l() { + self.next_period(); + + r.int_event(0).iclr().write(|w| { + w.set_l(true); + }); + } + + if mis.ccu0() { + self.next_period(); + + r.int_event(0).iclr().write(|w| { + w.set_ccu0(true); + }); + } + + if mis.ccu1() { + r.int_event(0).iclr().write(|w| { + w.set_ccu1(true); + }); + + self.trigger_alarm(cs); + } + }); + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let r = regs(); + let ctr = regs_counter(r); + + self.alarm.borrow(cs).set(timestamp); + + let t = self.now(); + + if timestamp <= t { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + r.int_event(0).imask().modify(|w| w.set_ccu1(false)); + + self.alarm.borrow(cs).set(u64::MAX); + + return false; + } + + // Write the CC1 value regardless of whether we're going to enable it now or not. + // This way, when we enable it later, the right value is already set. + ctr.cc(1).write(|w| { + w.set_ccval(timestamp as u16); + }); + + // Enable it if it'll happen soon. Otherwise, `next_period` will enable it. + let diff = timestamp - t; + r.int_event(0).imask().modify(|w| w.set_ccu1(diff < 0xC000)); + + // Reevaluate if the alarm timestamp is still in the future + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed since we set it, we have a race condition and + // the alarm may or may not have fired. + // Disarm the alarm and return `false` to indicate that. + // It is the caller's responsibility to handle this ambiguity. + r.int_event(0).imask().modify(|w| w.set_ccu1(false)); + + self.alarm.borrow(cs).set(u64::MAX); + + return false; + } + + // We're confident the alarm will ring in the future. + true + } +} + +impl Driver for TimxDriver { + fn now(&self) -> u64 { + let regs = regs(); + + let period = self.period.load(Ordering::Relaxed); + // Ensure the compiler does not read the counter before the period. + compiler_fence(Ordering::Acquire); + + let counter = regs_counter(regs).ctr().read().cctr() as u16; + + calc_now(period, counter) + } + + fn schedule_wake(&self, at: u64, waker: &Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }); + } +} + +embassy_time_driver::time_driver_impl!(static DRIVER: TimxDriver = TimxDriver { + period: AtomicU32::new(0), + alarm: Mutex::new(Cell::new(u64::MAX)), + queue: Mutex::new(RefCell::new(Queue::new())) +}); + +pub(crate) fn init(cs: CriticalSection) { + DRIVER.init(cs); +} + +#[cfg(time_driver_timg0)] +#[interrupt] +fn TIMG0() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg1)] +#[interrupt] +fn TIMG1() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg2)] +#[interrupt] +fn TIMG2() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg3)] +#[interrupt] +fn TIMG3() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg4)] +#[interrupt] +fn TIMG4() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg5)] +#[interrupt] +fn TIMG5() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg6)] +#[interrupt] +fn TIMG6() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg7)] +#[interrupt] +fn TIMG7() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg8)] +#[interrupt] +fn TIMG8() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg9)] +#[interrupt] +fn TIMG9() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg10)] +#[interrupt] +fn TIMG10() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_timg11)] +#[interrupt] +fn TIMG11() { + DRIVER.on_interrupt(); +} + +// TODO: TIMG12 and TIMG13 + +#[cfg(time_driver_timg14)] +#[interrupt] +fn TIMG14() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_tima0)] +#[interrupt] +fn TIMA0() { + DRIVER.on_interrupt(); +} + +#[cfg(time_driver_tima1)] +#[interrupt] +fn TIMA1() { + DRIVER.on_interrupt(); +} diff --git a/embassy-mspm0/src/timer.rs b/embassy-mspm0/src/timer.rs new file mode 100644 index 000000000..4441e5640 --- /dev/null +++ b/embassy-mspm0/src/timer.rs @@ -0,0 +1,48 @@ +#![macro_use] + +/// Amount of bits of a timer. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TimerBits { + /// 16 bits. + Bits16, + /// 32 bits. + Bits32, +} + +#[allow(private_bounds)] +pub trait Timer: SealedTimer + 'static { + /// Amount of bits this timer has. + const BITS: TimerBits; +} + +pub(crate) trait SealedTimer { + /// Registers for this timer. + /// + /// This is a raw pointer to the register block. The actual register block layout varies depending on the + /// timer type. + fn regs() -> *mut (); + + /// Enable the interrupt corresponding to this timer. + unsafe fn enable_interrupt(); +} + +macro_rules! impl_timer { + ($name: ident, $bits: ident) => { + impl crate::timer::SealedTimer for crate::peripherals::$name { + fn regs() -> *mut () { + crate::pac::$name.as_ptr() + } + + unsafe fn enable_interrupt() { + use embassy_hal_internal::interrupt::InterruptExt; + crate::interrupt::$name.unpend(); + crate::interrupt::$name.enable(); + } + } + + impl crate::timer::Timer for crate::peripherals::$name { + const BITS: crate::timer::TimerBits = crate::timer::TimerBits::$bits; + } + }; +} diff --git a/embassy-mspm0/src/uart.rs b/embassy-mspm0/src/uart.rs new file mode 100644 index 000000000..bc1d2e343 --- /dev/null +++ b/embassy-mspm0/src/uart.rs @@ -0,0 +1,1111 @@ +#![macro_use] + +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_internal::PeripheralType; + +use crate::gpio::{AnyPin, PfType, Pull, SealedPin}; +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::mode::{Blocking, Mode}; +use crate::pac::uart::{vals, Uart as Regs}; +use crate::Peri; + +/// The clock source for the UART. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockSel { + /// Use the low frequency clock. + /// + /// The LFCLK runs at 32.768 kHz. + LfClk, + + /// Use the middle frequency clock. + /// + /// The MCLK runs at 4 MHz. + MfClk, + // BusClk, + // BusClk depends on the timer's power domain. + // This will be implemented later. +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// The order of bits in byte. +pub enum BitOrder { + /// The most significant bit is first. + MsbFirst, + + /// The least significant bit is first. + LsbFirst, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Number of data bits +pub enum DataBits { + /// 5 Data Bits + DataBits5, + + /// 6 Data Bits + DataBits6, + + /// 7 Data Bits + DataBits7, + + /// 8 Data Bits + DataBits8, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Parity +pub enum Parity { + /// No parity + ParityNone, + + /// Even Parity + ParityEven, + + /// Odd Parity + ParityOdd, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Number of stop bits +pub enum StopBits { + /// One stop bit + Stop1, + + /// Two stop bits + Stop2, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Config Error +pub enum ConfigError { + /// Rx or Tx not enabled + RxOrTxNotEnabled, + + /// The baud rate could not be configured with the given clocks. + InvalidBaudRate, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// Config +pub struct Config { + /// UART clock source. + pub clock_source: ClockSel, + + /// Baud rate + pub baudrate: u32, + + /// Number of data bits. + pub data_bits: DataBits, + + /// Number of stop bits. + pub stop_bits: StopBits, + + /// Parity type. + pub parity: Parity, + + /// The order of bits in a transmitted/received byte. + pub msb_order: BitOrder, + + /// If true: the `TX` is internally connected to `RX`. + pub loop_back_enable: bool, + + // TODO: Pending way to check if uart is extended + // /// If true: [manchester coding] is used. + // /// + // /// [manchester coding]: https://en.wikipedia.org/wiki/Manchester_code + // pub manchester: bool, + + // TODO: majority voting + + // TODO: fifo level select - need power domain info in metapac + + // TODO: glitch suppression + /// If true: invert TX pin signal values (VDD = 0/mark, Gnd = 1/idle). + pub invert_tx: bool, + + /// If true: invert RX pin signal values (VDD = 0/mark, Gnd = 1/idle). + pub invert_rx: bool, + + /// If true: invert RTS pin signal values (VDD = 0/mark, Gnd = 1/idle). + pub invert_rts: bool, + + /// If true: invert CTS pin signal values (VDD = 0/mark, Gnd = 1/idle). + pub invert_cts: bool, + + /// Set the pull configuration for the TX pin. + pub tx_pull: Pull, + + /// Set the pull configuration for the RX pin. + pub rx_pull: Pull, + + /// Set the pull configuration for the RTS pin. + pub rts_pull: Pull, + + /// Set the pull configuration for the CTS pin. + pub cts_pull: Pull, +} + +impl Default for Config { + fn default() -> Self { + Self { + clock_source: ClockSel::MfClk, + baudrate: 115200, + data_bits: DataBits::DataBits8, + stop_bits: StopBits::Stop1, + parity: Parity::ParityNone, + // hardware default + msb_order: BitOrder::LsbFirst, + loop_back_enable: false, + // manchester: false, + invert_tx: false, + invert_rx: false, + invert_rts: false, + invert_cts: false, + tx_pull: Pull::None, + rx_pull: Pull::None, + rts_pull: Pull::None, + cts_pull: Pull::None, + } + } +} + +/// Bidirectional UART Driver, which acts as a combination of [`UartTx`] and [`UartRx`]. +/// +/// ### Notes on [`embedded_io::Read`] +/// +/// `embedded_io::Read` requires guarantees that the base [`UartRx`] cannot provide. +/// +/// See [`UartRx`] for more details, and see [`BufferedUart`] and [`RingBufferedUartRx`] +/// as alternatives that do provide the necessary guarantees for `embedded_io::Read`. +pub struct Uart<'d, M: Mode> { + tx: UartTx<'d, M>, + rx: UartRx<'d, M>, +} + +impl<'d, M: Mode> SetConfig for Uart<'d, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.tx.set_config(config)?; + self.rx.set_config(config) + } +} + +/// Serial error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + Framing, + + Noise, + + Overrun, + + Parity, + + Break, +} + +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let message = match self { + Self::Framing => "Framing Error", + Self::Noise => "Noise Error", + Self::Overrun => "RX Buffer Overrun", + Self::Parity => "Parity Check Error", + Self::Break => "Break Error", + }; + + write!(f, "{}", message) + } +} + +impl core::error::Error for Error {} + +/// Rx-only UART Driver. +/// +/// Can be obtained from [`Uart::split`], or can be constructed independently, +/// if you do not need the transmitting half of the driver. +pub struct UartRx<'d, M: Mode> { + info: &'static Info, + state: &'static State, + rx: Option>, + rts: Option>, + _phantom: PhantomData, +} + +impl<'d, M: Mode> SetConfig for UartRx<'d, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config(config) + } +} + +impl<'d> UartRx<'d, Blocking> { + /// Create a new rx-only UART with no hardware flow control. + /// + /// Useful if you only want Uart Rx. It saves 1 pin . + pub fn new_blocking( + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + config: Config, + ) -> Result { + Self::new_inner(peri, new_pin!(rx, config.rx_pf()), None, config) + } + + /// Create a new rx-only UART with a request-to-send pin + pub fn new_blocking_with_rts( + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_pf()), + new_pin!(rts, config.rts_pf()), + config, + ) + } +} + +impl<'d, M: Mode> UartRx<'d, M> { + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + if let Some(ref rx) = self.rx { + rx.update_pf(config.rx_pf()); + } + + if let Some(ref rts) = self.rts { + rts.update_pf(config.rts_pf()); + } + + reconfigure(self.info, self.state, config) + } + + /// Perform a blocking read into `buffer` + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + let r = self.info.regs; + + for b in buffer { + // Wait if nothing has arrived yet. + while r.stat().read().rxfe() {} + + // Prevent the compiler from reading from buffer too early + compiler_fence(Ordering::Acquire); + *b = read_with_error(r)?; + } + + Ok(()) + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), baudrate) + } +} + +impl<'d, M: Mode> Drop for UartRx<'d, M> { + fn drop(&mut self) { + self.rx.as_ref().map(|x| x.set_as_disconnected()); + self.rts.as_ref().map(|x| x.set_as_disconnected()); + } +} + +/// Tx-only UART Driver. +/// +/// Can be obtained from [`Uart::split`], or can be constructed independently, +/// if you do not need the receiving half of the driver. +pub struct UartTx<'d, M: Mode> { + info: &'static Info, + state: &'static State, + tx: Option>, + cts: Option>, + _phantom: PhantomData, +} + +impl<'d, M: Mode> SetConfig for UartTx<'d, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + reconfigure(self.info, self.state, config) + } +} + +impl<'d> UartTx<'d, Blocking> { + /// Create a new blocking tx-only UART with no hardware flow control. + /// + /// Useful if you only want Uart Tx. It saves 1 pin. + pub fn new_blocking( + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + config: Config, + ) -> Result { + Self::new_inner(peri, new_pin!(tx, config.tx_pf()), None, config) + } + + /// Create a new blocking tx-only UART with a clear-to-send pin + pub fn new_blocking_with_cts( + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + cts: Peri<'d, impl CtsPin>, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(tx, config.tx_pf()), + new_pin!(cts, config.cts_pf()), + config, + ) + } +} + +impl<'d, M: Mode> UartTx<'d, M> { + /// Reconfigure the driver + pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { + if let Some(ref tx) = self.tx { + tx.update_pf(config.tx_pf()); + } + + if let Some(ref cts) = self.cts { + cts.update_pf(config.cts_pf()); + } + + reconfigure(self.info, self.state, config) + } + + /// Perform a blocking UART write + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = self.info.regs; + + for &b in buffer { + // Wait if there is no space + while !r.stat().read().txfe() {} + + // Prevent the compiler from writing to buffer too early + compiler_fence(Ordering::Release); + r.txdata().write(|w| { + w.set_data(b); + }); + } + + Ok(()) + } + + /// Block until transmission complete + pub fn blocking_flush(&mut self) -> Result<(), Error> { + let r = self.info.regs; + + // Wait until TX fifo/buffer is empty + while r.stat().read().txfe() {} + Ok(()) + } + + /// Send break character + pub fn send_break(&self) { + let r = self.info.regs; + + r.lcrh().modify(|w| { + w.set_brk(true); + }); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(&self.info, self.state.clock.load(Ordering::Relaxed), baudrate) + } +} + +impl<'d, M: Mode> Drop for UartTx<'d, M> { + fn drop(&mut self) { + self.tx.as_ref().map(|x| x.set_as_disconnected()); + self.cts.as_ref().map(|x| x.set_as_disconnected()); + } +} + +impl<'d> Uart<'d, Blocking> { + /// Create a new blocking bidirectional UART. + pub fn new_blocking( + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_pf()), + new_pin!(tx, config.tx_pf()), + None, + None, + config, + ) + } + + /// Create a new bidirectional UART with request-to-send and clear-to-send pins + pub fn new_blocking_with_rtscts( + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_pf()), + new_pin!(tx, config.tx_pf()), + new_pin!(rts, config.rts_pf()), + new_pin!(cts, config.cts_pf()), + config, + ) + } +} + +impl<'d, M: Mode> Uart<'d, M> { + /// Perform a blocking write + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.blocking_write(buffer) + } + + /// Block until transmission complete + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Perform a blocking read into `buffer` + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.blocking_read(buffer) + } + + /// Split the Uart into a transmitter and receiver, which is + /// particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut UartTx<'d, M>, &mut UartRx<'d, M>) { + (&mut self.tx, &mut self.rx) + } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break(); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(&self.tx.info, self.tx.state.clock.load(Ordering::Relaxed), baudrate) + } +} + +/// Peripheral instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType { + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +/// UART `TX` pin trait +pub trait TxPin: crate::gpio::Pin { + /// Get the PF number needed to use this pin as `TX`. + fn pf_num(&self) -> u8; +} + +/// UART `RX` pin trait +pub trait RxPin: crate::gpio::Pin { + /// Get the PF number needed to use this pin as `RX`. + fn pf_num(&self) -> u8; +} + +/// UART `CTS` pin trait +pub trait CtsPin: crate::gpio::Pin { + /// Get the PF number needed to use this pin as `CTS`. + fn pf_num(&self) -> u8; +} + +/// UART `RTS` pin trait +pub trait RtsPin: crate::gpio::Pin { + /// Get the PF number needed to use this pin as `RTS`. + fn pf_num(&self) -> u8; +} + +// ==== IMPL types ==== + +pub(crate) struct Info { + pub(crate) regs: Regs, + pub(crate) interrupt: Interrupt, +} + +pub(crate) struct State { + /// The clock rate of the UART. This might be configured. + pub(crate) clock: AtomicU32, +} + +impl<'d, M: Mode> UartRx<'d, M> { + fn new_inner( + _peri: Peri<'d, T>, + rx: Option>, + rts: Option>, + config: Config, + ) -> Result { + let mut this = Self { + info: T::info(), + state: T::state(), + rx, + rts, + _phantom: PhantomData, + }; + this.enable_and_configure(&config)?; + + Ok(this) + } + + fn enable_and_configure(&mut self, config: &Config) -> Result<(), ConfigError> { + let info = self.info; + + enable(info.regs); + configure(info, self.state, config, true, self.rts.is_some(), false, false)?; + + Ok(()) + } +} + +impl<'d, M: Mode> UartTx<'d, M> { + fn new_inner( + _peri: Peri<'d, T>, + tx: Option>, + cts: Option>, + config: Config, + ) -> Result { + let mut this = Self { + info: T::info(), + state: T::state(), + tx, + cts, + _phantom: PhantomData, + }; + this.enable_and_configure(&config)?; + + Ok(this) + } + + fn enable_and_configure(&mut self, config: &Config) -> Result<(), ConfigError> { + let info = self.info; + let state = self.state; + + enable(info.regs); + configure(info, state, config, false, false, true, self.cts.is_some())?; + + Ok(()) + } +} + +impl<'d, M: Mode> Uart<'d, M> { + fn new_inner( + _peri: Peri<'d, T>, + rx: Option>, + tx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) -> Result { + let info = T::info(); + let state = T::state(); + + let mut this = Self { + tx: UartTx { + info, + state, + tx, + cts, + _phantom: PhantomData, + }, + rx: UartRx { + info, + state, + rx, + rts, + _phantom: PhantomData, + }, + }; + this.enable_and_configure(&config)?; + + Ok(this) + } + + fn enable_and_configure(&mut self, config: &Config) -> Result<(), ConfigError> { + let info = self.rx.info; + let state = self.rx.state; + + enable(info.regs); + configure( + info, + state, + config, + true, + self.rx.rts.is_some(), + true, + self.tx.cts.is_some(), + )?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) + } +} + +impl Config { + fn tx_pf(&self) -> PfType { + PfType::output(self.tx_pull, self.invert_tx) + } + + fn rx_pf(&self) -> PfType { + PfType::input(self.rx_pull, self.invert_rx) + } + + fn rts_pf(&self) -> PfType { + PfType::output(self.rts_pull, self.invert_rts) + } + + fn cts_pf(&self) -> PfType { + PfType::input(self.rts_pull, self.invert_rts) + } +} + +fn enable(regs: Regs) { + let gprcm = regs.gprcm(0); + + gprcm.rstctl().write(|w| { + w.set_resetstkyclr(true); + w.set_resetassert(true); + w.set_key(vals::ResetKey::KEY); + }); + + gprcm.pwren().write(|w| { + w.set_enable(true); + w.set_key(vals::PwrenKey::KEY); + }); +} + +fn configure( + info: &Info, + state: &State, + config: &Config, + enable_rx: bool, + enable_rts: bool, + enable_tx: bool, + enable_cts: bool, +) -> Result<(), ConfigError> { + let r = info.regs; + + if !enable_rx && !enable_tx { + return Err(ConfigError::RxOrTxNotEnabled); + } + + // SLAU846B says that clocks should be enabled before disabling the uart. + r.clksel().write(|w| match config.clock_source { + ClockSel::LfClk => { + w.set_lfclk_sel(true); + w.set_mfclk_sel(false); + w.set_busclk_sel(false); + } + ClockSel::MfClk => { + w.set_mfclk_sel(true); + w.set_lfclk_sel(false); + w.set_busclk_sel(false); + } + }); + + let clock = match config.clock_source { + ClockSel::LfClk => 32768, + ClockSel::MfClk => 4_000_000, + }; + + state.clock.store(clock, Ordering::Relaxed); + + info.regs.ctl0().modify(|w| { + w.set_lbe(config.loop_back_enable); + w.set_rxe(enable_rx); + w.set_txe(enable_tx); + // RXD_OUT_EN and TXD_OUT_EN? + w.set_menc(false); + w.set_mode(vals::Mode::UART); + w.set_rtsen(enable_rts); + w.set_ctsen(enable_cts); + // oversampling is set later + // TODO: config + w.set_fen(false); + // TODO: config + w.set_majvote(false); + w.set_msbfirst(matches!(config.msb_order, BitOrder::MsbFirst)); + }); + + info.regs.lcrh().modify(|w| { + let eps = if matches!(config.parity, Parity::ParityEven) { + vals::Eps::EVEN + } else { + vals::Eps::ODD + }; + + let wlen = match config.data_bits { + DataBits::DataBits5 => vals::Wlen::DATABIT5, + DataBits::DataBits6 => vals::Wlen::DATABIT6, + DataBits::DataBits7 => vals::Wlen::DATABIT7, + DataBits::DataBits8 => vals::Wlen::DATABIT8, + }; + + // Used in LIN mode only + w.set_brk(false); + w.set_pen(config.parity != Parity::ParityNone); + w.set_eps(eps); + w.set_stp2(matches!(config.stop_bits, StopBits::Stop2)); + w.set_wlen(wlen); + // appears to only be used in RS-485 mode. + w.set_sps(false); + // IDLE pattern? + w.set_sendidle(false); + // ignore extdir_setup and extdir_hold, only used in RS-485 mode. + }); + + set_baudrate_inner(info.regs, clock, config.baudrate)?; + + r.ctl0().modify(|w| { + w.set_enable(true); + }); + + Ok(()) +} + +fn reconfigure(info: &Info, state: &State, config: &Config) -> Result<(), ConfigError> { + info.interrupt.disable(); + let r = info.regs; + let ctl0 = r.ctl0().read(); + configure(info, state, config, ctl0.rxe(), ctl0.rtsen(), ctl0.txe(), ctl0.ctsen())?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) +} + +/// Set the baud rate and clock settings. +/// +/// This should be done relatively late during configuration since some clock settings are invalid depending on mode. +fn set_baudrate(info: &Info, clock: u32, baudrate: u32) -> Result<(), ConfigError> { + let r = info.regs; + + info.interrupt.disable(); + + // Programming baud rate requires that the peripheral is disabled + critical_section::with(|_cs| { + r.ctl0().modify(|w| { + w.set_enable(false); + }); + }); + + // Wait for end of transmission per suggestion in SLAU 845 section 18.3.28 + while !r.stat().read().txfe() {} + + set_baudrate_inner(r, clock, baudrate)?; + + critical_section::with(|_cs| { + r.ctl0().modify(|w| { + w.set_enable(true); + }); + }); + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) +} + +fn set_baudrate_inner(regs: Regs, clock: u32, baudrate: u32) -> Result<(), ConfigError> { + // Quoting SLAU846 section 18.2.3.4: + // "When IBRD = 0, FBRD is ignored and no data gets transferred by the UART." + const MIN_IBRD: u16 = 1; + + // FBRD can be 0 + // FBRD is at most a 6-bit number. + const MAX_FBRD: u8 = 2_u8.pow(6); + + const DIVS: [(u8, vals::Clkdiv); 8] = [ + (1, vals::Clkdiv::DIV_BY_1), + (2, vals::Clkdiv::DIV_BY_2), + (3, vals::Clkdiv::DIV_BY_3), + (4, vals::Clkdiv::DIV_BY_4), + (5, vals::Clkdiv::DIV_BY_5), + (6, vals::Clkdiv::DIV_BY_6), + (7, vals::Clkdiv::DIV_BY_7), + (8, vals::Clkdiv::DIV_BY_8), + ]; + + // Quoting from SLAU 846 section 18.2.3.4: + // "Select oversampling by 3 or 8 to achieve higher speed with UARTclk/8 or UARTclk/3. In this case + // the receiver tolerance to clock deviation is reduced." + // + // "Select oversampling by 16 to increase the tolerance of the receiver to clock deviations. The + // maximum speed is limited to UARTclk/16." + // + // Based on these requirements, prioritize higher oversampling first to increase tolerance to clock + // deviation. If no valid BRD value can be found satisifying the highest sample rate, then reduce + // sample rate until valid parameters are found. + const OVS: [(u8, vals::Hse); 3] = [(16, vals::Hse::OVS16), (8, vals::Hse::OVS8), (3, vals::Hse::OVS3)]; + + // 3x oversampling is not supported with manchester coding, DALI or IrDA. + let x3_invalid = { + let ctl0 = regs.ctl0().read(); + let irctl = regs.irctl().read(); + + ctl0.menc() || matches!(ctl0.mode(), vals::Mode::DALI) || irctl.iren() + }; + let mut found = None; + + 'outer: for &(oversampling, hse_value) in &OVS { + if matches!(hse_value, vals::Hse::OVS3) && x3_invalid { + continue; + } + + // Verify that the selected oversampling does not require a clock faster than what the hardware + // is provided. + let Some(min_clock) = baudrate.checked_mul(oversampling as u32) else { + trace!( + "{}x oversampling would cause overflow for clock: {} Hz", + oversampling, + clock + ); + continue; + }; + + if min_clock > clock { + trace!("{} oversampling is too high for clock: {} Hz", oversampling, clock); + continue; + } + + for &(div, div_value) in &DIVS { + trace!( + "Trying div: {}, oversampling {} for {} baud", + div, + oversampling, + baudrate + ); + + let Some((ibrd, fbrd)) = calculate_brd(clock, div, baudrate, oversampling) else { + trace!("Calculating BRD overflowed: trying another divider"); + continue; + }; + + if ibrd < MIN_IBRD || fbrd > MAX_FBRD { + trace!("BRD was invalid: trying another divider"); + continue; + } + + found = Some((hse_value, div_value, ibrd, fbrd)); + break 'outer; + } + } + + let Some((hse, div, ibrd, fbrd)) = found else { + return Err(ConfigError::InvalidBaudRate); + }; + + regs.clkdiv().write(|w| { + w.set_ratio(div); + }); + + regs.ibrd().write(|w| { + w.set_divint(ibrd); + }); + + regs.fbrd().write(|w| { + w.set_divfrac(fbrd); + }); + + regs.ctl0().modify(|w| { + w.set_hse(hse); + }); + + Ok(()) +} + +/// Calculate the integer and fractional parts of the `BRD` value. +/// +/// Returns [`None`] if calculating this results in overflows. +/// +/// Values returned are `(ibrd, fbrd)` +fn calculate_brd(clock: u32, div: u8, baud: u32, oversampling: u8) -> Option<(u16, u8)> { + use fixed::types::U26F6; + + // Calculate BRD according to SLAU 846 section 18.2.3.4. + // + // BRD is a 22-bit value with 16 integer bits and 6 fractional bits. + // + // uart_clock = clock / div + // brd = ibrd.fbrd = uart_clock / (oversampling * baud)" + // + // It is tempting to rearrange the equation such that there is only a single division in + // order to reduce error. However this is wrong since the denominator ends up being too + // small to represent in 6 fraction bits. This means that FBRD would always be 0. + // + // Calculations are done in a U16F6 format. However the fixed crate has no such representation. + // U26F6 is used since it has the same number of fractional bits and we verify at the end that + // the integer part did not overflow. + let clock = U26F6::from_num(clock); + let div = U26F6::from_num(div); + let oversampling = U26F6::from_num(oversampling); + let baud = U26F6::from_num(baud); + + let uart_clock = clock.checked_div(div)?; + + // oversampling * baud + let denom = oversampling.checked_mul(baud)?; + // uart_clock / (oversampling * baud) + let brd = uart_clock.checked_div(denom)?; + + // Checked is used to determine overflow in the 10 most singificant bits since the + // actual representation of BRD is U16F6. + let ibrd = brd.checked_to_num::()?; + + // We need to scale FBRD's representation to an integer. + let fbrd_scale = U26F6::from_num(2_u32.checked_pow(U26F6::FRAC_NBITS)?); + + // It is suggested that 0.5 is added to ensure that any fractional parts round up to the next + // integer. If it doesn't round up then it'll get discarded which is okay. + let half = U26F6::from_num(1) / U26F6::from_num(2); + // fbrd = INT(((FRAC(BRD) * 64) + 0.5)) + let fbrd = brd + .frac() + .checked_mul(fbrd_scale)? + .checked_add(half)? + .checked_to_num::()?; + + Some((ibrd, fbrd)) +} + +fn read_with_error(r: Regs) -> Result { + let rx = r.rxdata().read(); + + if rx.frmerr() { + return Err(Error::Framing); + } else if rx.parerr() { + return Err(Error::Parity); + } else if rx.brkerr() { + return Err(Error::Break); + } else if rx.ovrerr() { + return Err(Error::Overrun); + } else if rx.nerr() { + return Err(Error::Noise); + } + + Ok(rx.data()) +} + +pub(crate) trait SealedInstance { + fn info() -> &'static Info; + fn state() -> &'static State; +} + +macro_rules! impl_uart_instance { + ($instance: ident) => { + impl crate::uart::SealedInstance for crate::peripherals::$instance { + fn info() -> &'static crate::uart::Info { + use crate::interrupt::typelevel::Interrupt; + use crate::uart::Info; + + const INFO: Info = Info { + regs: crate::pac::$instance, + interrupt: crate::interrupt::typelevel::$instance::IRQ, + }; + &INFO + } + + fn state() -> &'static crate::uart::State { + use crate::interrupt::typelevel::Interrupt; + use crate::uart::State; + + static STATE: State = State { + clock: core::sync::atomic::AtomicU32::new(0), + }; + &STATE + } + } + + impl crate::uart::Instance for crate::peripherals::$instance { + type Interrupt = crate::interrupt::typelevel::$instance; + } + }; +} + +macro_rules! impl_uart_tx_pin { + ($instance: ident, $pin: ident, $pf: expr) => { + impl crate::uart::TxPin for crate::peripherals::$pin { + fn pf_num(&self) -> u8 { + $pf + } + } + }; +} + +macro_rules! impl_uart_rx_pin { + ($instance: ident, $pin: ident, $pf: expr) => { + impl crate::uart::RxPin for crate::peripherals::$pin { + fn pf_num(&self) -> u8 { + $pf + } + } + }; +} + +macro_rules! impl_uart_cts_pin { + ($instance: ident, $pin: ident, $pf: expr) => { + impl crate::uart::CtsPin for crate::peripherals::$pin { + fn pf_num(&self) -> u8 { + $pf + } + } + }; +} + +macro_rules! impl_uart_rts_pin { + ($instance: ident, $pin: ident, $pf: expr) => { + impl crate::uart::RtsPin for crate::peripherals::$pin { + fn pf_num(&self) -> u8 { + $pf + } + } + }; +} + +#[cfg(test)] +mod tests { + use super::calculate_brd; + + /// This is a smoke test based on the example in SLAU 846 section 18.2.3.4. + #[test] + fn datasheet() { + let brd = calculate_brd(40_000_000, 1, 19200, 16); + + assert!(matches!(brd, Some((130, 13)))); + } +} diff --git a/embassy-net-adin1110/Cargo.toml b/embassy-net-adin1110/Cargo.toml index 0b2a6744d..a620928cb 100644 --- a/embassy-net-adin1110/Cargo.toml +++ b/embassy-net-adin1110/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-net-adin1110" -version = "0.2.0" +version = "0.3.0" description = "embassy-net driver for the ADIN1110 ethernet chip" keywords = ["embedded", "ADIN1110", "embassy-net", "embedded-hal-async", "ethernet"] categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] @@ -11,13 +11,13 @@ documentation = "https://docs.embassy.dev/embassy-net-adin1110" [dependencies] heapless = "0.8" -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4", default-features = false, optional = true } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-bus = { version = "0.1", features = ["async"] } embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } -embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } bitfield = "0.14.0" @@ -29,7 +29,7 @@ critical-section = { version = "1.1.2", features = ["std"] } futures-test = "0.3.28" [features] -defmt = [ "dep:defmt", "embedded-hal-1/defmt-03" ] +defmt = ["dep:defmt", "embedded-hal-1/defmt-03"] log = ["dep:log"] [package.metadata.embassy_docs] diff --git a/embassy-net-driver-channel/Cargo.toml b/embassy-net-driver-channel/Cargo.toml index abce9315b..c16c4be74 100644 --- a/embassy-net-driver-channel/Cargo.toml +++ b/embassy-net-driver-channel/Cargo.toml @@ -22,9 +22,9 @@ target = "thumbv7em-none-eabi" features = ["defmt"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs index 7ad4d449e..600efd9e5 100644 --- a/embassy-net-driver-channel/src/lib.rs +++ b/embassy-net-driver-channel/src/lib.rs @@ -26,13 +26,11 @@ pub struct State { } impl State { - const NEW_PACKET: PacketBuf = PacketBuf::new(); - /// Create a new channel state. pub const fn new() -> Self { Self { - rx: [Self::NEW_PACKET; N_RX], - tx: [Self::NEW_PACKET; N_TX], + rx: [const { PacketBuf::new() }; N_RX], + tx: [const { PacketBuf::new() }; N_TX], inner: MaybeUninit::uninit(), } } @@ -326,8 +324,14 @@ pub struct Device<'d, const MTU: usize> { } impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { - type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ; - type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; + type RxToken<'a> + = RxToken<'a, MTU> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, MTU> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { if self.rx.poll_receive(cx).is_ready() && self.tx.poll_send(cx).is_ready() { diff --git a/embassy-net-driver/Cargo.toml b/embassy-net-driver/Cargo.toml index 97e8a0db3..34bc6c91a 100644 --- a/embassy-net-driver/Cargo.toml +++ b/embassy-net-driver/Cargo.toml @@ -22,4 +22,4 @@ target = "thumbv7em-none-eabi" features = ["defmt"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs index 87f9f6ed1..4c847718d 100644 --- a/embassy-net-driver/src/lib.rs +++ b/embassy-net-driver/src/lib.rs @@ -83,10 +83,12 @@ pub trait Driver { } impl Driver for &mut T { - type RxToken<'a> = T::RxToken<'a> + type RxToken<'a> + = T::RxToken<'a> where Self: 'a; - type TxToken<'a> = T::TxToken<'a> + type TxToken<'a> + = T::TxToken<'a> where Self: 'a; diff --git a/embassy-net-enc28j60/Cargo.toml b/embassy-net-enc28j60/Cargo.toml index cafced4b2..b26be1420 100644 --- a/embassy-net-enc28j60/Cargo.toml +++ b/embassy-net-enc28j60/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-net-enc28j60" -version = "0.1.0" +version = "0.2.0" description = "embassy-net driver for the ENC28J60 ethernet chip" keywords = ["embedded", "enc28j60", "embassy-net", "embedded-hal-async", "ethernet"] categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] @@ -13,10 +13,10 @@ documentation = "https://docs.embassy.dev/embassy-net-enc28j60" embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } -embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } [package.metadata.embassy_docs] diff --git a/embassy-net-enc28j60/src/lib.rs b/embassy-net-enc28j60/src/lib.rs index dda35f498..c1f32719a 100644 --- a/embassy-net-enc28j60/src/lib.rs +++ b/embassy-net-enc28j60/src/lib.rs @@ -635,11 +635,13 @@ where S: SpiDevice, O: OutputPin, { - type RxToken<'a> = RxToken<'a> + type RxToken<'a> + = RxToken<'a> where Self: 'a; - type TxToken<'a> = TxToken<'a, S, O> + type TxToken<'a> + = TxToken<'a, S, O> where Self: 'a; diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml index 915eba7a0..dea74ed65 100644 --- a/embassy-net-esp-hosted/Cargo.toml +++ b/embassy-net-esp-hosted/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-net-esp-hosted" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = "embassy-net driver for ESP-Hosted" keywords = ["embedded", "esp-hosted", "embassy-net", "embedded-hal-async", "wifi"] @@ -10,15 +10,15 @@ repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/embassy-net-esp-hosted" [features] -defmt = [ "dep:defmt", "heapless/defmt-03" ] -log = [ "dep:log" ] +defmt = ["dep:defmt", "heapless/defmt-03"] +log = ["dep:log"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } -embassy-time = { version = "0.3.2", path = "../embassy-time" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync"} +embassy-time = { version = "0.4.0", path = "../embassy-time" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"} embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} diff --git a/embassy-net-esp-hosted/README.md b/embassy-net-esp-hosted/README.md index 524231e6c..e1afb1483 100644 --- a/embassy-net-esp-hosted/README.md +++ b/embassy-net-esp-hosted/README.md @@ -1,6 +1,6 @@ # ESP-Hosted `embassy-net` integration -[`embassy-net`](https://crates.io/crates/embassy-net) integration for Espressif SoCs running the the [ESP-Hosted](https://github.com/espressif/esp-hosted) stack. +[`embassy-net`](https://crates.io/crates/embassy-net) integration for Espressif SoCs running the [ESP-Hosted](https://github.com/espressif/esp-hosted) stack. See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf52840) directory for usage examples with the nRF52840. diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs index c8cea8503..b1838a425 100644 --- a/embassy-net-esp-hosted/src/control.rs +++ b/embassy-net-esp-hosted/src/control.rs @@ -120,7 +120,7 @@ impl<'a> Control<'a> { pwd: unwrap!(String::try_from(password)), bssid: String::new(), listen_interval: 3, - is_wpa3_supported: false, + is_wpa3_supported: true, }; ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); self.state_ch.set_link_state(LinkState::Up); diff --git a/embassy-net-esp-hosted/src/ioctl.rs b/embassy-net-esp-hosted/src/ioctl.rs index e2a6815aa..512023206 100644 --- a/embassy-net-esp-hosted/src/ioctl.rs +++ b/embassy-net-esp-hosted/src/ioctl.rs @@ -1,5 +1,5 @@ use core::cell::RefCell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::task::Poll; use embassy_sync::waitqueue::WakerRegistration; @@ -38,7 +38,7 @@ impl Shared { })) } - pub async fn ioctl_wait_complete(&self) -> usize { + pub fn ioctl_wait_complete(&self) -> impl Future + '_ { poll_fn(|cx| { let mut this = self.0.borrow_mut(); if let IoctlState::Done { resp_len } = this.ioctl { @@ -48,7 +48,6 @@ impl Shared { Poll::Pending } }) - .await } pub async fn ioctl_wait_pending(&self) -> PendingIoctl { @@ -108,7 +107,7 @@ impl Shared { this.control_waker.wake(); } - pub async fn init_wait(&self) { + pub fn init_wait(&self) -> impl Future + '_ { poll_fn(|cx| { let mut this = self.0.borrow_mut(); if this.is_init { @@ -118,6 +117,5 @@ impl Shared { Poll::Pending } }) - .await } } diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs index c78578bf1..f05e2a70a 100644 --- a/embassy-net-esp-hosted/src/lib.rs +++ b/embassy-net-esp-hosted/src/lib.rs @@ -137,7 +137,7 @@ where let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); let state_ch = ch_runner.state_runner(); - let mut runner = Runner { + let runner = Runner { ch: ch_runner, state_ch, shared: &state.shared, @@ -148,7 +148,6 @@ where spi, heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, }; - runner.init().await; (device, Control::new(state_ch, &state.shared), runner) } @@ -174,8 +173,6 @@ where IN: InputPin + Wait, OUT: OutputPin, { - async fn init(&mut self) {} - /// Run the packet processing. pub async fn run(mut self) -> ! { debug!("resetting..."); diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml new file mode 100644 index 000000000..62813e546 --- /dev/null +++ b/embassy-net-nrf91/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "embassy-net-nrf91" +version = "0.1.0" +edition = "2021" +description = "embassy-net driver for Nordic nRF91-series cellular modems" +keywords = ["embedded", "nrf91", "embassy-net", "cellular"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-nrf91" + +[features] +defmt = ["dep:defmt", "heapless/defmt-03"] +log = ["dep:log"] + +[dependencies] +defmt = { version = "1.0.1", optional = true } +log = { version = "0.4.14", optional = true } + +nrf-pac = "0.1.0" +cortex-m = "0.7.7" + +embassy-time = { version = "0.4.0", path = "../embassy-time" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } + +heapless = "0.8" +embedded-io = "0.6.1" +at-commands = "0.5.4" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy-net-nrf91/README.md b/embassy-net-nrf91/README.md new file mode 100644 index 000000000..30da71787 --- /dev/null +++ b/embassy-net-nrf91/README.md @@ -0,0 +1,9 @@ +# nRF91 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) driver for Nordic nRF91-series cellular modems. + +See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs new file mode 100644 index 000000000..2dda615c1 --- /dev/null +++ b/embassy-net-nrf91/src/context.rs @@ -0,0 +1,362 @@ +//! Helper utility to configure a specific modem context. +use core::net::IpAddr; +use core::str::FromStr; + +use at_commands::builder::CommandBuilder; +use at_commands::parser::CommandParser; +use embassy_time::{Duration, Timer}; +use heapless::Vec; + +/// Provides a higher level API for controlling a given context. +pub struct Control<'a> { + control: crate::Control<'a>, + cid: u8, +} + +/// Configuration for a given context +pub struct Config<'a> { + /// Desired APN address. + pub apn: &'a [u8], + /// Desired authentication protocol. + pub auth_prot: AuthProt, + /// Credentials. + pub auth: Option<(&'a [u8], &'a [u8])>, + /// SIM pin + pub pin: Option<&'a [u8]>, +} + +/// Authentication protocol. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum AuthProt { + /// No authentication. + None = 0, + /// PAP authentication. + Pap = 1, + /// CHAP authentication. + Chap = 2, +} + +/// Error returned by control. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Not enough space for command. + BufferTooSmall, + /// Error parsing response from modem. + AtParseError, + /// Error parsing IP addresses. + AddrParseError, +} + +impl From for Error { + fn from(_: at_commands::parser::ParseError) -> Self { + Self::AtParseError + } +} + +/// Status of a given context. +#[derive(PartialEq, Debug)] +pub struct Status { + /// Attached to APN or not. + pub attached: bool, + /// IP if assigned. + pub ip: Option, + /// Gateway if assigned. + pub gateway: Option, + /// DNS servers if assigned. + pub dns: Vec, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Status { + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!(f, "attached: {}", self.attached); + if let Some(ip) = &self.ip { + defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip)); + } + } +} + +impl<'a> Control<'a> { + /// Create a new instance of a control handle for a given context. + /// + /// Will wait for the modem to be initialized if not. + pub async fn new(control: crate::Control<'a>, cid: u8) -> Self { + control.wait_init().await; + Self { control, cid } + } + + /// Perform a raw AT command + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + self.control.at_command(req, resp).await + } + + /// Configures the modem with the provided config. + /// + /// NOTE: This will disconnect the modem from any current APN and should not + /// be called if the configuration has not been changed. + /// + /// After configuring, invoke [`enable()`] to activate the configuration. + pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGDCONT") + .with_int_parameter(self.cid) + .with_string_parameter("IP") + .with_string_parameter(config.apn) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + // info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let mut op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGAUTH") + .with_int_parameter(self.cid) + .with_int_parameter(config.auth_prot as u8); + if let Some((username, password)) = config.auth { + op = op.with_string_parameter(username).with_string_parameter(password); + } + let op = op.finish().map_err(|_| Error::BufferTooSmall)?; + + let n = self.control.at_command(op, &mut buf).await; + // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + if let Some(pin) = config.pin { + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CPIN") + .with_string_parameter(pin) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let _ = self.control.at_command(op, &mut buf).await; + // Ignore ERROR which means no pin required + } + + Ok(()) + } + + /// Attach to the PDN + pub async fn attach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Read current connectivity status for modem. + pub async fn detach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + async fn attached(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res,) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + Ok(res == 1) + } + + /// Read current connectivity status for modem. + pub async fn status(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res,) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + let attached = res == 1; + if !attached { + return Ok(Status { + attached, + ip: None, + gateway: None, + dns: Vec::new(), + }); + } + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGPADDR") + .with_int_parameter(self.cid) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_, ip1, _ip2) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGPADDR: ") + .expect_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + + let ip = if let Some(ip) = ip1 { + let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; + Some(ip) + } else { + None + }; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGCONTRDP") + .with_int_parameter(self.cid) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGCONTRDP: ") + .expect_int_parameter() + .expect_optional_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + + let gateway = if let Some(ip) = gateway { + if ip.is_empty() { + None + } else { + Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + } + } else { + None + }; + + let mut dns = Vec::new(); + if let Some(ip) = dns1 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); + } + + if let Some(ip) = dns2 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); + } + + Ok(Status { + attached, + ip, + gateway, + dns, + }) + } + + async fn wait_attached(&self) -> Result { + while !self.attached().await? { + Timer::after(Duration::from_secs(1)).await; + } + let status = self.status().await?; + Ok(status) + } + + /// Disable modem + pub async fn disable(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + Ok(()) + } + + /// Enable modem + pub async fn enable(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + // Make modem survive PDN detaches + let op = CommandBuilder::create_set(&mut cmd, true) + .named("%XPDNCFG") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Run a control loop for this context, ensuring that reaattach is handled. + pub async fn run(&self, reattach: F) -> Result<(), Error> { + self.enable().await?; + let status = self.wait_attached().await?; + let mut fd = self.control.open_raw_socket().await; + reattach(&status); + + loop { + if !self.attached().await? { + trace!("detached"); + + self.control.close_raw_socket(fd).await; + let status = self.wait_attached().await?; + trace!("attached"); + fd = self.control.open_raw_socket().await; + reattach(&status); + } + Timer::after(Duration::from_secs(10)).await; + } + } +} diff --git a/embassy-net-nrf91/src/fmt.rs b/embassy-net-nrf91/src/fmt.rs new file mode 100644 index 000000000..35b929fde --- /dev/null +++ b/embassy-net-nrf91/src/fmt.rs @@ -0,0 +1,274 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs new file mode 100644 index 000000000..61fcaea1f --- /dev/null +++ b/embassy-net-nrf91/src/lib.rs @@ -0,0 +1,1069 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] +#![deny(unused_must_use)] + +// must be first +mod fmt; + +pub mod context; + +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::mem::{self, MaybeUninit}; +use core::ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping}; +use core::slice; +use core::sync::atomic::{compiler_fence, fence, Ordering}; +use core::task::{Poll, Waker}; + +use cortex_m::peripheral::NVIC; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe; +use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; +use heapless::Vec; +use {embassy_net_driver_channel as ch, nrf_pac as pac}; + +const RX_SIZE: usize = 8 * 1024; +const TRACE_SIZE: usize = 16 * 1024; +const TRACE_BUF: usize = 1024; +const MTU: usize = 1500; + +/// Network driver. +/// +/// This is the type you have to pass to `embassy-net` when creating the network stack. +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// Call this function on IPC IRQ +pub fn on_ipc_irq() { + trace!("irq"); + + pac::IPC_NS.inten().write(|_| ()); + WAKER.wake(); +} + +struct Allocator<'a> { + start: *mut u8, + end: *mut u8, + _phantom: PhantomData<&'a mut u8>, +} + +impl<'a> Allocator<'a> { + fn alloc_bytes(&mut self, size: usize) -> &'a mut [MaybeUninit] { + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = self.start; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is in-bounds. + unsafe { slice::from_raw_parts_mut(p as *mut _, size) } + } + + fn alloc(&mut self) -> &'a mut MaybeUninit { + let align = mem::align_of::(); + let size = mem::size_of::(); + + let align_size = match (self.start as usize) % align { + 0 => 0, + n => align - n, + }; + + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if align_size + size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = unsafe { self.start.add(align_size) }; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is aligned and in-bounds. + unsafe { &mut *(p as *mut _) } + } +} + +/// Create a new nRF91 embassy-net driver. +pub async fn new<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], +) -> (NetDriver<'a>, Control<'a>, Runner<'a>) { + let (n, c, r, _) = new_internal(state, shmem, None).await; + (n, c, r) +} + +/// Create a new nRF91 embassy-net driver with trace. +pub async fn new_with_trace<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: &'a mut TraceBuffer, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, TraceReader<'a>) { + let (n, c, r, t) = new_internal(state, shmem, Some(trace_buffer)).await; + (n, c, r, t.unwrap()) +} + +/// Create a new nRF91 embassy-net driver. +async fn new_internal<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: Option<&'a mut TraceBuffer>, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, Option>) { + let shmem_len = shmem.len(); + let shmem_ptr = shmem.as_mut_ptr() as *mut u8; + + const SPU_REGION_SIZE: usize = 8192; // 8kb + assert!(shmem_len != 0); + assert!( + shmem_len % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize) % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize + shmem_len) < 0x2002_0000, + "shmem must be in the lower 128kb of RAM" + ); + + let spu = pac::SPU_S; + debug!("Setting IPC RAM as nonsecure..."); + let region_start = (shmem_ptr as usize - 0x2000_0000) / SPU_REGION_SIZE; + let region_end = region_start + shmem_len / SPU_REGION_SIZE; + for i in region_start..region_end { + spu.ramregion(i).perm().write(|w| { + w.set_execute(true); + w.set_write(true); + w.set_read(true); + w.set_secattr(false); + w.set_lock(false); + }) + } + + spu.periphid(42).perm().write(|w| w.set_secattr(false)); + + let mut alloc = Allocator { + start: shmem_ptr, + end: unsafe { shmem_ptr.add(shmem_len) }, + _phantom: PhantomData, + }; + + let ipc = pac::IPC_NS; + let power = pac::POWER_S; + + let cb: &mut ControlBlock = alloc.alloc().write(unsafe { mem::zeroed() }); + let rx = alloc.alloc_bytes(RX_SIZE); + let trace = alloc.alloc_bytes(TRACE_SIZE); + + cb.version = 0x00010000; + cb.rx_base = rx.as_mut_ptr() as _; + cb.rx_size = RX_SIZE; + cb.control_list_ptr = &mut cb.lists[0]; + cb.data_list_ptr = &mut cb.lists[1]; + cb.modem_info_ptr = &mut cb.modem_info; + cb.trace_ptr = &mut cb.trace; + cb.lists[0].len = LIST_LEN; + cb.lists[1].len = LIST_LEN; + cb.trace.base = trace.as_mut_ptr() as _; + cb.trace.size = TRACE_SIZE; + + ipc.gpmem(0).write_value(cb as *mut _ as u32); + ipc.gpmem(1).write_value(0); + + // connect task/event i to channel i + for i in 0..8 { + ipc.send_cnf(i).write(|w| w.0 = 1 << i); + ipc.receive_cnf(i).write(|w| w.0 = 1 << i); + } + + compiler_fence(Ordering::SeqCst); + + // POWER.LTEMODEM.STARTN = 0 + // The reg is missing in the PAC?? + let startn = unsafe { (power.as_ptr() as *mut u32).add(0x610 / 4) }; + unsafe { startn.write_volatile(0) } + + unsafe { NVIC::unmask(pac::Interrupt::IPC) }; + + let state_inner = &*state.inner.write(RefCell::new(StateInner { + init: false, + init_waker: WakerRegistration::new(), + cb, + requests: [const { None }; REQ_COUNT], + next_req_serial: 0x12345678, + net_fd: None, + + rx_control_list: ptr::null_mut(), + rx_data_list: ptr::null_mut(), + rx_seq_no: 0, + rx_check: PointerChecker { + start: rx.as_mut_ptr() as *mut u8, + end: (rx.as_mut_ptr() as *mut u8).wrapping_add(RX_SIZE), + }, + + tx_seq_no: 0, + tx_buf_used: [false; TX_BUF_COUNT], + tx_waker: WakerRegistration::new(), + + trace_chans: Vec::new(), + trace_check: PointerChecker { + start: trace.as_mut_ptr() as *mut u8, + end: (trace.as_mut_ptr() as *mut u8).wrapping_add(TRACE_SIZE), + }, + })); + + let control = Control { state: state_inner }; + + let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ip); + let state_ch = ch_runner.state_runner(); + state_ch.set_link_state(ch::driver::LinkState::Up); + + let (trace_reader, trace_writer) = if let Some(trace) = trace_buffer { + let (r, w) = trace.trace.split(); + (Some(r), Some(w)) + } else { + (None, None) + }; + + let runner = Runner { + ch: ch_runner, + state: state_inner, + trace_writer, + }; + + (device, control, runner, trace_reader) +} + +/// State holding modem traces. +pub struct TraceBuffer { + trace: pipe::Pipe, +} + +/// Represents writer half of the trace buffer. +pub type TraceWriter<'a> = pipe::Writer<'a, NoopRawMutex, TRACE_BUF>; + +/// Represents the reader half of the trace buffer. +pub type TraceReader<'a> = pipe::Reader<'a, NoopRawMutex, TRACE_BUF>; + +impl TraceBuffer { + /// Create a new TraceBuffer. + pub const fn new() -> Self { + Self { + trace: pipe::Pipe::new(), + } + } +} + +/// Shared state for the driver. +pub struct State { + ch: ch::State, + inner: MaybeUninit>, +} + +impl State { + /// Create a new State. + pub const fn new() -> Self { + Self { + ch: ch::State::new(), + inner: MaybeUninit::uninit(), + } + } +} + +const TX_BUF_COUNT: usize = 4; +const TX_BUF_SIZE: usize = 1500; + +struct TraceChannelInfo { + ptr: *mut TraceChannel, + start: *mut u8, + end: *mut u8, +} + +const REQ_COUNT: usize = 4; + +struct PendingRequest { + req_serial: u32, + resp_msg: *mut Message, + waker: Waker, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct NoFreeBufs; + +struct StateInner { + init: bool, + init_waker: WakerRegistration, + + cb: *mut ControlBlock, + requests: [Option; REQ_COUNT], + next_req_serial: u32, + + net_fd: Option, + + rx_control_list: *mut List, + rx_data_list: *mut List, + rx_seq_no: u16, + rx_check: PointerChecker, + + tx_seq_no: u16, + tx_buf_used: [bool; TX_BUF_COUNT], + tx_waker: WakerRegistration, + + trace_chans: Vec, + trace_check: PointerChecker, +} + +impl StateInner { + fn poll(&mut self, trace_writer: &mut Option>, ch: &mut ch::Runner) { + trace!("poll!"); + let ipc = pac::IPC_NS; + + if ipc.events_receive(0).read() != 0 { + ipc.events_receive(0).write_value(0); + trace!("ipc 0"); + } + + if ipc.events_receive(2).read() != 0 { + ipc.events_receive(2).write_value(0); + trace!("ipc 2"); + + if !self.init { + let desc = unsafe { addr_of!((*self.cb).modem_info).read_volatile() }; + assert_eq!(desc.version, 1); + + self.rx_check.check_mut(desc.control_list_ptr); + self.rx_check.check_mut(desc.data_list_ptr); + + self.rx_control_list = desc.control_list_ptr; + self.rx_data_list = desc.data_list_ptr; + let rx_control_len = unsafe { addr_of!((*self.rx_control_list).len).read_volatile() }; + let rx_data_len = unsafe { addr_of!((*self.rx_data_list).len).read_volatile() }; + assert_eq!(rx_control_len, LIST_LEN); + assert_eq!(rx_data_len, LIST_LEN); + self.init = true; + + debug!("IPC initialized OK!"); + self.init_waker.wake(); + } + } + + if ipc.events_receive(4).read() != 0 { + ipc.events_receive(4).write_value(0); + trace!("ipc 4"); + + loop { + let list = unsafe { &mut *self.rx_control_list }; + let control_work = self.process(list, true, ch); + let list = unsafe { &mut *self.rx_data_list }; + let data_work = self.process(list, false, ch); + if !control_work && !data_work { + break; + } + } + } + + if ipc.events_receive(6).read() != 0 { + ipc.events_receive(6).write_value(0); + trace!("ipc 6"); + } + + if ipc.events_receive(7).read() != 0 { + ipc.events_receive(7).write_value(0); + trace!("ipc 7: trace"); + + let msg = unsafe { addr_of!((*self.cb).trace.rx_state).read_volatile() }; + if msg != 0 { + trace!("trace msg {}", msg); + match msg { + 0 => unreachable!(), + 1 => { + let ctx = unsafe { addr_of!((*self.cb).trace.rx_ptr).read_volatile() } as *mut TraceContext; + debug!("trace init: {:?}", ctx); + self.trace_check.check(ctx); + let chans = unsafe { addr_of!((*ctx).chans).read_volatile() }; + for chan_ptr in chans { + let chan = self.trace_check.check_read(chan_ptr); + self.trace_check.check(chan.start); + self.trace_check.check(chan.end); + assert!(chan.start < chan.end); + self.trace_chans + .push(TraceChannelInfo { + ptr: chan_ptr, + start: chan.start, + end: chan.end, + }) + .map_err(|_| ()) + .unwrap() + } + } + 2 => { + for chan_info in &self.trace_chans { + let read_ptr = unsafe { addr_of!((*chan_info.ptr).read_ptr).read_volatile() }; + let write_ptr = unsafe { addr_of!((*chan_info.ptr).write_ptr).read_volatile() }; + assert!(read_ptr >= chan_info.start && read_ptr <= chan_info.end); + assert!(write_ptr >= chan_info.start && write_ptr <= chan_info.end); + if read_ptr != write_ptr { + let id = unsafe { addr_of!((*chan_info.ptr).id).read_volatile() }; + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + if read_ptr < write_ptr { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, write_ptr.offset_from(read_ptr) as _) + }); + } else { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, chan_info.end.offset_from(read_ptr) as _) + }); + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts( + chan_info.start, + write_ptr.offset_from(chan_info.start) as _, + ) + }); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + unsafe { addr_of_mut!((*chan_info.ptr).read_ptr).write_volatile(write_ptr) }; + } + } + } + _ => warn!("unknown trace msg {}", msg), + } + unsafe { addr_of_mut!((*self.cb).trace.rx_state).write_volatile(0) }; + } + } + + ipc.intenset().write(|w| { + w.set_receive0(true); + w.set_receive2(true); + w.set_receive4(true); + w.set_receive6(true); + w.set_receive7(true); + }); + } + + fn handle_trace(writer: &mut Option>, id: u8, data: &[u8]) { + if let Some(writer) = writer { + trace!("trace: {} {}", id, data.len()); + let mut header = [0u8; 5]; + header[0] = 0xEF; + header[1] = 0xBE; + header[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes()); + header[4] = id; + writer.try_write(&header).ok(); + writer.try_write(data).ok(); + } + } + + fn process(&mut self, list: *mut List, is_control: bool, ch: &mut ch::Runner) -> bool { + let mut did_work = false; + for i in 0..LIST_LEN { + let item_ptr = unsafe { addr_of_mut!((*list).items[i]) }; + let preamble = unsafe { addr_of!((*item_ptr).state).read_volatile() }; + if preamble & 0xFF == 0x01 && preamble >> 16 == self.rx_seq_no as u32 { + let msg_ptr = unsafe { addr_of!((*item_ptr).message).read_volatile() }; + let msg = self.rx_check.check_read(msg_ptr); + + debug!("rx seq {} msg: {:?}", preamble >> 16, msg); + + if is_control { + self.handle_control(&msg); + } else { + self.handle_data(&msg, ch); + } + + unsafe { addr_of_mut!((*item_ptr).state).write_volatile(0x03) }; + self.rx_seq_no = self.rx_seq_no.wrapping_add(1); + + did_work = true; + } + } + did_work + } + + fn find_free_message(&mut self, ch: usize) -> Option { + for i in 0..LIST_LEN { + let preamble = unsafe { addr_of!((*self.cb).lists[ch].items[i].state).read_volatile() }; + if matches!(preamble & 0xFF, 0 | 3) { + trace!("using tx msg idx {}", i); + return Some(i); + } + } + return None; + } + + fn find_free_tx_buf(&mut self) -> Option { + for i in 0..TX_BUF_COUNT { + if !self.tx_buf_used[i] { + trace!("using tx buf idx {}", i); + return Some(i); + } + } + return None; + } + + fn send_message(&mut self, msg: &mut Message, data: &[u8]) -> Result<(), NoFreeBufs> { + if data.is_empty() { + msg.data = ptr::null_mut(); + msg.data_len = 0; + self.send_message_raw(msg) + } else { + assert!(data.len() <= TX_BUF_SIZE); + let buf_idx = self.find_free_tx_buf().ok_or(NoFreeBufs)?; + let buf = unsafe { addr_of_mut!((*self.cb).tx_bufs[buf_idx]) } as *mut u8; + unsafe { copy_nonoverlapping(data.as_ptr(), buf, data.len()) } + msg.data = buf; + msg.data_len = data.len(); + self.tx_buf_used[buf_idx] = true; + + fence(Ordering::SeqCst); // synchronize copy_nonoverlapping (non-volatile) with volatile writes below. + if let Err(e) = self.send_message_raw(msg) { + msg.data = ptr::null_mut(); + msg.data_len = 0; + self.tx_buf_used[buf_idx] = false; + self.tx_waker.wake(); + Err(e) + } else { + Ok(()) + } + } + } + + fn send_message_raw(&mut self, msg: &Message) -> Result<(), NoFreeBufs> { + let (ch, ipc_ch) = match msg.channel { + 1 => (0, 1), // control + 2 => (1, 3), // data + _ => unreachable!(), + }; + + // allocate a msg. + let idx = self.find_free_message(ch).ok_or(NoFreeBufs)?; + + debug!("tx seq {} msg: {:?}", self.tx_seq_no, msg); + + let msg_slot = unsafe { addr_of_mut!((*self.cb).msgs[ch][idx]) }; + unsafe { msg_slot.write_volatile(*msg) } + let list_item = unsafe { addr_of_mut!((*self.cb).lists[ch].items[idx]) }; + unsafe { addr_of_mut!((*list_item).message).write_volatile(msg_slot) } + unsafe { addr_of_mut!((*list_item).state).write_volatile((self.tx_seq_no as u32) << 16 | 0x01) } + self.tx_seq_no = self.tx_seq_no.wrapping_add(1); + + let ipc = pac::IPC_NS; + ipc.tasks_send(ipc_ch).write_value(1); + Ok(()) + } + + fn handle_control(&mut self, msg: &Message) { + match msg.id >> 16 { + 1 => debug!("control msg: modem ready"), + 2 => self.handle_control_free(msg.data), + _ => warn!("unknown control message id {:08x}", msg.id), + } + } + + fn handle_control_free(&mut self, ptr: *mut u8) { + let base = unsafe { addr_of!((*self.cb).tx_bufs) } as usize; + let ptr = ptr as usize; + + if ptr < base { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + let diff = ptr - base; + let idx = diff / TX_BUF_SIZE; + + if idx >= TX_BUF_COUNT || idx * TX_BUF_SIZE != diff { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + trace!("control free pointer {:08x} idx {}", ptr, idx); + if !self.tx_buf_used[idx] { + warn!( + "control free pointer {:08x} idx {}: buffer was already free??", + ptr, idx + ); + } + self.tx_buf_used[idx] = false; + self.tx_waker.wake(); + } + + fn handle_data(&mut self, msg: &Message, ch: &mut ch::Runner) { + if !msg.data.is_null() { + self.rx_check.check_length(msg.data, msg.data_len); + } + + let freed = match msg.id & 0xFFFF { + // AT + 3 => { + match msg.id >> 16 { + // AT request ack + 2 => false, + // AT response + 3 => self.handle_resp(msg), + // AT notification + 4 => false, + x => { + warn!("received unknown AT kind {}", x); + false + } + } + } + // IP + 4 => { + match msg.id >> 28 { + // IP response + 8 => self.handle_resp(msg), + // IP notification + 9 => match (msg.id >> 16) & 0xFFF { + // IP receive notification + 1 => { + if let Some(buf) = ch.try_rx_buf() { + let mut len = msg.data_len; + if len > buf.len() { + warn!("truncating rx'd packet from {} to {} bytes", len, buf.len()); + len = buf.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, buf.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + ch.rx_done(len); + } + false + } + _ => false, + }, + x => { + warn!("received unknown IP kind {}", x); + false + } + } + } + x => { + warn!("received unknown kind {}", x); + false + } + }; + + if !freed { + self.send_free(msg); + } + } + + fn handle_resp(&mut self, msg: &Message) -> bool { + let req_serial = u32::from_le_bytes(msg.param[0..4].try_into().unwrap()); + if req_serial == 0 { + return false; + } + + for optr in &mut self.requests { + if let Some(r) = optr { + if r.req_serial == req_serial { + let r = optr.take().unwrap(); + unsafe { r.resp_msg.write(*msg) } + r.waker.wake(); + *optr = None; + return true; + } + } + } + + warn!( + "resp with id {} serial {} doesn't match any pending req", + msg.id, req_serial + ); + false + } + + fn send_free(&mut self, msg: &Message) { + if msg.data.is_null() { + return; + } + + let mut free_msg: Message = unsafe { mem::zeroed() }; + free_msg.channel = 1; // control + free_msg.id = 0x20001; // free + free_msg.data = msg.data; + free_msg.data_len = msg.data_len; + + unwrap!(self.send_message_raw(&free_msg)); + } +} + +struct PointerChecker { + start: *mut u8, + end: *mut u8, +} + +impl PointerChecker { + // check the pointer is in bounds in the arena, panic otherwise. + fn check_length(&self, ptr: *const u8, len: usize) { + assert!(ptr as usize >= self.start as usize); + let end_ptr = (ptr as usize).checked_add(len).unwrap(); + assert!(end_ptr <= self.end as usize); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check(&self, ptr: *const T) { + assert!(ptr.is_aligned()); + self.check_length(ptr as *const u8, mem::size_of::()); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_read(&self, ptr: *const T) -> T { + self.check(ptr); + unsafe { ptr.read_volatile() } + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_mut(&self, ptr: *mut T) { + self.check(ptr as *const T) + } +} + +/// Control handle for the driver. +/// +/// You can use this object to control the modem at runtime, such as running AT commands. +pub struct Control<'a> { + state: &'a RefCell, +} + +impl<'a> Control<'a> { + /// Wait for modem IPC to be initialized. + pub fn wait_init(&self) -> impl Future + '_ { + poll_fn(|cx| { + let mut state = self.state.borrow_mut(); + if state.init { + return Poll::Ready(()); + } + state.init_waker.register(cx.waker()); + Poll::Pending + }) + } + + async fn request(&self, msg: &mut Message, req_data: &[u8], resp_data: &mut [u8]) -> usize { + // get waker + let waker = poll_fn(|cx| Poll::Ready(cx.waker().clone())).await; + + // Send request + let mut state = self.state.borrow_mut(); + let mut req_serial = state.next_req_serial; + if msg.id & 0xFFFF == 3 { + // AT response seems to keep only the lower 8 bits. Others do keep the full 32 bits..?? + req_serial &= 0xFF; + } + + // increment next_req_serial, skip zero because we use it as an "ignore" value. + // We have to skip when the *lowest byte* is zero because AT responses. + state.next_req_serial = state.next_req_serial.wrapping_add(1); + if state.next_req_serial & 0xFF == 0 { + state.next_req_serial = state.next_req_serial.wrapping_add(1); + } + + drop(state); // don't borrow state across awaits. + + msg.param[0..4].copy_from_slice(&req_serial.to_le_bytes()); + + poll_fn(|cx| { + let mut state = self.state.borrow_mut(); + state.tx_waker.register(cx.waker()); + match state.send_message(msg, req_data) { + Ok(_) => Poll::Ready(()), + Err(NoFreeBufs) => Poll::Pending, + } + }) + .await; + + // Setup the pending request state. + let mut state = self.state.borrow_mut(); + let (req_slot_idx, req_slot) = state + .requests + .iter_mut() + .enumerate() + .find(|(_, x)| x.is_none()) + .unwrap(); + msg.id = 0; // zero out id, so when it becomes nonzero we know the req is done. + let msg_ptr: *mut Message = msg; + *req_slot = Some(PendingRequest { + req_serial, + resp_msg: msg_ptr, + waker, + }); + + drop(state); // don't borrow state across awaits. + + // On cancel, unregister the request slot. + let _drop = OnDrop::new(|| { + // Remove request slot. + let mut state = self.state.borrow_mut(); + let slot = &mut state.requests[req_slot_idx]; + if let Some(s) = slot { + if s.req_serial == req_serial { + *slot = None; + } + } + + // If cancelation raced with actually receiving the response, + // we own the data, so we have to free it. + let msg = unsafe { &mut *msg_ptr }; + if msg.id != 0 { + state.send_free(msg); + } + }); + // Wait for response. + poll_fn(|_| { + // we have to use the raw pointer and not the original reference `msg` + // because that'd invalidate the raw ptr that's still stored in `req_slot`. + if unsafe { (*msg_ptr).id } != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + _drop.defuse(); + + if msg.data.is_null() { + // no response data. + return 0; + } + + // Copy response data out, if any. + // Pointer was validated in StateInner::handle_data(). + let mut len = msg.data_len; + if len > resp_data.len() { + warn!("truncating response data from {} to {}", len, resp_data.len()); + len = resp_data.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, resp_data.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + self.state.borrow_mut().send_free(msg); + len + } + + /// Run an AT command. + /// + /// The response is written in `resp` and its length returned. + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x0001_0003; // AT command + msg.param_len = 4; + + self.request(&mut msg, req, resp).await + } + + /// Open the raw socket used for sending/receiving IP packets. + /// + /// This must be done after `AT+CFUN=1` (?) + async fn open_raw_socket(&self) -> u32 { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7001_0004; // open socket + msg.param_len = 20; + + let param = [ + 0xFF, 0xFF, 0xFF, 0xFF, // req_serial + 0xFF, 0xFF, 0xFF, 0xFF, // ??? + 0x05, 0x00, 0x00, 0x00, // family + 0x03, 0x00, 0x00, 0x00, // type + 0x00, 0x00, 0x00, 0x00, // protocol + ]; + msg.param[..param.len()].copy_from_slice(¶m); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80010004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + assert_eq!(msg.param_len, 16); + let fd = u32::from_le_bytes(msg.param[12..16].try_into().unwrap()); + self.state.borrow_mut().net_fd.replace(fd); + + trace!("got FD: {}", fd); + fd + } + + async fn close_raw_socket(&self, fd: u32) { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7009_0004; // close socket + msg.param_len = 8; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80090004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + } +} + +/// Background runner for the driver. +pub struct Runner<'a> { + ch: ch::Runner<'a, MTU>, + state: &'a RefCell, + trace_writer: Option>, +} + +impl<'a> Runner<'a> { + /// Run the driver operation in the background. + /// + /// You must run this in a background task, concurrently with all network operations. + pub async fn run(mut self) -> ! { + poll_fn(|cx| { + WAKER.register(cx.waker()); + + let mut state = self.state.borrow_mut(); + state.poll(&mut self.trace_writer, &mut self.ch); + + if let Poll::Ready(buf) = self.ch.poll_tx_buf(cx) { + if let Some(fd) = state.net_fd { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7006_0004; // IP send + msg.param_len = 12; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + if let Err(e) = state.send_message(&mut msg, buf) { + warn!("tx failed: {:?}", e); + } + self.ch.tx_done(); + } + } + + Poll::Pending + }) + .await + } +} + +const LIST_LEN: usize = 16; + +#[repr(C)] +struct ControlBlock { + version: u32, + rx_base: *mut u8, + rx_size: usize, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + modem_info_ptr: *mut ModemInfo, + trace_ptr: *mut Trace, + unk: u32, + + modem_info: ModemInfo, + trace: Trace, + + // 0 = control, 1 = data + lists: [List; 2], + msgs: [[Message; LIST_LEN]; 2], + + tx_bufs: [[u8; TX_BUF_SIZE]; TX_BUF_COUNT], +} + +#[repr(C)] +struct ModemInfo { + version: u32, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + padding: [u32; 5], +} + +#[repr(C)] +struct Trace { + size: usize, + base: *mut u8, + tx_state: u32, + tx_ptr: *mut u8, + rx_state: u32, + rx_ptr: *mut u8, + unk1: u32, + unk2: u32, +} + +const TRACE_CHANNEL_COUNT: usize = 3; + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceContext { + unk1: u32, + unk2: u32, + len: u32, + chans: [*mut TraceChannel; TRACE_CHANNEL_COUNT], +} + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceChannel { + id: u8, + unk1: u8, + unk2: u8, + unk3: u8, + write_ptr: *mut u8, + read_ptr: *mut u8, + start: *mut u8, + end: *mut u8, +} + +#[repr(C)] +struct List { + len: usize, + items: [ListItem; LIST_LEN], +} + +#[repr(C)] +struct ListItem { + /// top 16 bits: seqno + /// bottom 8 bits: + /// 0x01: sent + /// 0x02: held + /// 0x03: freed + state: u32, + message: *mut Message, +} + +#[repr(C)] +#[derive(defmt::Format, Clone, Copy)] +struct Message { + id: u32, + + /// 1 = control, 2 = data + channel: u8, + unk1: u8, + unk2: u8, + unk3: u8, + + data: *mut u8, + data_len: usize, + param_len: usize, + param: [u8; 44], +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy-net-ppp/CHANGELOG.md b/embassy-net-ppp/CHANGELOG.md new file mode 100644 index 000000000..6ce64ddcb --- /dev/null +++ b/embassy-net-ppp/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog for embassy-net-ppp + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.2.0 - 2025-01-12 + +- Update `ppproto` to v0.2. +- Use `core::net` IP types for IPv4 configuration instead of a custom type. + +## 0.1.0 - 2024-01-12 + +First release. diff --git a/embassy-net-ppp/Cargo.toml b/embassy-net-ppp/Cargo.toml index f6371f955..0936626eb 100644 --- a/embassy-net-ppp/Cargo.toml +++ b/embassy-net-ppp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-net-ppp" -version = "0.1.0" +version = "0.2.0" description = "embassy-net driver for PPP over Serial" keywords = ["embedded", "ppp", "embassy-net", "embedded-hal-async", "async"] categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] @@ -14,14 +14,14 @@ defmt = ["dep:defmt", "ppproto/defmt"] log = ["dep:log", "ppproto/log"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } embedded-io-async = { version = "0.6.1" } embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -ppproto = { version = "0.1.2"} -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +ppproto = { version = "0.2.1"} +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-ppp-v$VERSION/embassy-net-ppp/src/" diff --git a/embassy-net-tuntap/src/lib.rs b/embassy-net-tuntap/src/lib.rs index 56f55fba1..2ff23f462 100644 --- a/embassy-net-tuntap/src/lib.rs +++ b/embassy-net-tuntap/src/lib.rs @@ -152,8 +152,14 @@ impl TunTapDevice { } impl Driver for TunTapDevice { - type RxToken<'a> = RxToken where Self: 'a; - type TxToken<'a> = TxToken<'a> where Self: 'a; + type RxToken<'a> + = RxToken + where + Self: 'a; + type TxToken<'a> + = TxToken<'a> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { let mut buf = vec![0; self.device.get_ref().mtu]; diff --git a/embassy-net-wiznet/Cargo.toml b/embassy-net-wiznet/Cargo.toml index e7fb3f455..a06a09302 100644 --- a/embassy-net-wiznet/Cargo.toml +++ b/embassy-net-wiznet/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-net-wiznet" -version = "0.1.0" +version = "0.2.0" description = "embassy-net driver for WIZnet SPI Ethernet chips" keywords = ["embedded", "embassy-net", "embedded-hal-async", "ethernet", "async"] categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] @@ -13,9 +13,9 @@ documentation = "https://docs.embassy.dev/embassy-net-wiznet" embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } -embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-wiznet-v$VERSION/embassy-net-wiznet/src/" diff --git a/embassy-net-wiznet/README.md b/embassy-net-wiznet/README.md index 786aab18d..b66619821 100644 --- a/embassy-net-wiznet/README.md +++ b/embassy-net-wiznet/README.md @@ -2,7 +2,7 @@ [`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet SPI ethernet chips, operating in MACRAW mode. -See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module. +See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico)) module. ## Supported chips diff --git a/embassy-net/CHANGELOG.md b/embassy-net/CHANGELOG.md index 56e245b92..8773772ce 100644 --- a/embassy-net/CHANGELOG.md +++ b/embassy-net/CHANGELOG.md @@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +No unreleased changes yet... Quick, go send a PR! + +## 0.7 - 2025-05-06 + +- don't infinite loop if udp::send methods receive a buffer too large to ever be sent +- add ICMP sockets and a ping utility +- configurable rate_limit for the ping utility +- Feature match udp sockets + +## 0.6 - 2025-01-05 + +- Make `Config` constructors `const` +- The `std` feature has been removed +- Updated `embassy-time` to v0.4 + +## 0.5 - 2024-11-28 + +- Refactor the API structure, simplifying lifetimes and generics. + - Stack is now a thin handle that implements `Copy+Clone`. Instead of passing `&Stack` around, you can now pass `Stack`. + - `Stack` and `DnsSocket` no longer need a generic parameter for the device driver. + - The `run()` method has been moved to a new `Runner` struct. + - Sockets are covariant wrt their lifetime. + - An implication of the refactor is now you need only one `StaticCell` instead of two if you need to share the network stack between tasks. +- Use standard `core::net` IP types instead of custom ones from smoltcp. +- Update to `smoltcp` v0.12. +- Add `mdns` Cargo feature. +- dns: properly handle `AddrType::Either` in `get_host_by_name()` +- dns: truncate instead of panic if the DHCP server gives us more DNS servers than the configured maximum. +- stack: add `wait_link_up()`, `wait_link_down()`, `wait_config_down()`. +- tcp: Add `recv_queue()`, `send_queue()`. +- tcp: Add `wait_read_ready()`, `wait_write_ready()`. +- tcp: allow setting timeout through `embedded-nal` client. +- tcp: fix `flush()` hanging forever if socket is closed with pending data. +- tcp: fix `flush()` not waiting for ACK of FIN. +- tcp: implement `ReadReady`, `WriteReady` traits from `embedded-io`. +- udp, raw: Add `wait_send_ready()`, `wait_recv_ready()`, `flush()`. +- udp: add `recv_from_with()`, `send_to_with()` methods, allowing for IO with one less copy. +- udp: send/recv now takes/returns full `UdpMetadata` instead of just the remote `IpEndpoint`. +- raw: add raw sockets. + + ## 0.4 - 2024-01-11 - Update to `embassy-time` v0.3. diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index d86a30755..526c8a4b3 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-net" -version = "0.4.0" +version = "0.7.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Async TCP/IP network stack for embedded systems" @@ -16,18 +16,15 @@ 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", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] +features = ["defmt", "tcp", "udp", "raw", "dns", "icmp", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] target = "thumbv7em-none-eabi" [package.metadata.docs.rs] -features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] +features = ["defmt", "tcp", "udp", "raw", "dns", "icmp", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] [features] -default = [] -std = [] - ## Enable defmt -defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03"] +defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03", "defmt?/ip_in_core"] ## Trace all raw received and transmitted packets using defmt or log. packet-trace = [] @@ -36,6 +33,8 @@ packet-trace = [] #! the [smoltcp feature flag documentation](https://github.com/smoltcp-rs/smoltcp#feature-flags) #! for more details +## Enable ICMP support +icmp = ["smoltcp/socket-icmp"] ## Enable UDP support udp = ["smoltcp/socket-udp"] ## Enable Raw support @@ -44,6 +43,8 @@ raw = ["smoltcp/socket-raw"] tcp = ["smoltcp/socket-tcp"] ## Enable DNS support dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"] +## Enable mDNS support +mdns = ["dns", "smoltcp/socket-mdns"] ## Enable DHCPv4 support dhcpv4 = ["proto-ipv4", "medium-ethernet", "smoltcp/socket-dhcpv4"] ## Enable DHCPv4 support with hostname @@ -58,25 +59,29 @@ medium-ethernet = ["smoltcp/medium-ethernet"] medium-ip = ["smoltcp/medium-ip"] ## Enable the IEEE 802.15.4 medium medium-ieee802154 = ["smoltcp/medium-ieee802154"] -## Enable IGMP support -igmp = ["smoltcp/proto-igmp"] +## Enable multicast support (for both ipv4 and/or ipv6 if enabled) +multicast = ["smoltcp/multicast"] +## Enable smoltcp std feature (necessary if using "managed" crate std feature) +std = ["smoltcp/std"] +## Enable smoltcp alloc feature (necessary if using "managed" crate alloc feature) +alloc = ["smoltcp/alloc"] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } -smoltcp = { version = "0.11.0", default-features = false, features = [ +smoltcp = { version = "0.12.0", default-features = false, features = [ "socket", "async", ] } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } -embassy-time = { version = "0.3.2", path = "../embassy-time" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embedded-io-async = { version = "0.6.1" } managed = { version = "0.8.0", default-features = false, features = [ "map" ] } heapless = { version = "0.8", default-features = false } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" document-features = "0.2.7" diff --git a/embassy-net/README.md b/embassy-net/README.md index 6b7d46934..1722ffc7b 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -10,8 +10,9 @@ memory management designed to work well for embedded systems, aiming for a more - IPv4, IPv6 - Ethernet and bare-IP mediums. -- TCP, UDP, DNS, DHCPv4, IGMPv4 +- TCP, UDP, DNS, DHCPv4 - TCP sockets implement the `embedded-io` async traits. +- Multicast See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and unimplemented features of the network protocols. diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 8ccfa4e4f..dbe73776c 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -9,7 +9,7 @@ pub use smoltcp::socket::dns::{DnsQuery, Socket}; pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; pub use smoltcp::wire::{DnsQueryType, IpAddress}; -use crate::{Driver, Stack}; +use crate::Stack; /// Errors returned by DnsSocket. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -44,21 +44,15 @@ impl From for Error { /// This exists only for compatibility with crates that use `embedded-nal-async`. /// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're /// not using `embedded-nal-async`. -pub struct DnsSocket<'a, D> -where - D: Driver + 'static, -{ - stack: &'a Stack, +pub struct DnsSocket<'a> { + stack: Stack<'a>, } -impl<'a, D> DnsSocket<'a, D> -where - D: Driver + 'static, -{ +impl<'a> DnsSocket<'a> { /// Create a new DNS socket using the provided stack. /// /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. - pub fn new(stack: &'a Stack) -> Self { + pub fn new(stack: Stack<'a>) -> Self { Self { stack } } @@ -72,18 +66,18 @@ where } } -impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D> -where - D: Driver + 'static, -{ +impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { type Error = Error; async fn get_host_by_name( &self, host: &str, addr_type: embedded_nal_async::AddrType, - ) -> Result { - use embedded_nal_async::{AddrType, IpAddr}; + ) -> Result { + use core::net::IpAddr; + + use embedded_nal_async::AddrType; + let (qtype, secondary_qtype) = match addr_type { AddrType::IPv4 => (DnsQueryType::A, None), AddrType::IPv6 => (DnsQueryType::Aaaa, None), @@ -107,20 +101,20 @@ where if let Some(first) = addrs.get(0) { Ok(match first { #[cfg(feature = "proto-ipv4")] - IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()), + IpAddress::Ipv4(addr) => IpAddr::V4(*addr), #[cfg(feature = "proto-ipv6")] - IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()), + IpAddress::Ipv6(addr) => IpAddr::V6(*addr), }) } else { Err(Error::Failed) } } - async fn get_host_by_address( - &self, - _addr: embedded_nal_async::IpAddr, - _result: &mut [u8], - ) -> Result { + async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result { todo!() } } + +fn _assert_covariant<'a, 'b: 'a>(x: DnsSocket<'b>) -> DnsSocket<'a> { + x +} diff --git a/embassy-net/src/device.rs b/embassy-net/src/driver_util.rs similarity index 89% rename from embassy-net/src/device.rs rename to embassy-net/src/driver_util.rs index 3b1d3c47c..536f4c3d9 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/driver_util.rs @@ -18,8 +18,14 @@ impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T> where T: Driver, { - type RxToken<'a> = RxTokenAdapter> where Self: 'a; - type TxToken<'a> = TxTokenAdapter> where Self: 'a; + type RxToken<'a> + = RxTokenAdapter> + where + Self: 'a; + type TxToken<'a> + = TxTokenAdapter> + where + Self: 'a; fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { self.inner @@ -74,11 +80,11 @@ where { fn consume(self, f: F) -> R where - F: FnOnce(&mut [u8]) -> R, + F: FnOnce(&[u8]) -> R, { self.0.consume(|buf| { #[cfg(feature = "packet-trace")] - trace!("rx: {:?}", buf); + trace!("embassy device rx: {:02x}", buf); f(buf) }) } @@ -99,7 +105,7 @@ where self.0.consume(len, |buf| { let r = f(buf); #[cfg(feature = "packet-trace")] - trace!("tx: {:?}", buf); + trace!("embassy device tx: {:02x}", buf); r }) } diff --git a/embassy-net/src/icmp.rs b/embassy-net/src/icmp.rs new file mode 100644 index 000000000..22c31a589 --- /dev/null +++ b/embassy-net/src/icmp.rs @@ -0,0 +1,859 @@ +//! ICMP sockets. + +use core::future::{poll_fn, Future}; +use core::mem; +use core::task::{Context, Poll}; + +use smoltcp::iface::{Interface, SocketHandle}; +pub use smoltcp::phy::ChecksumCapabilities; +use smoltcp::socket::icmp; +pub use smoltcp::socket::icmp::{Endpoint as IcmpEndpoint, PacketMetadata}; +use smoltcp::wire::IpAddress; +#[cfg(feature = "proto-ipv4")] +pub use smoltcp::wire::{Icmpv4Message, Icmpv4Packet, Icmpv4Repr}; +#[cfg(feature = "proto-ipv6")] +pub use smoltcp::wire::{Icmpv6Message, Icmpv6Packet, Icmpv6Repr}; + +use crate::Stack; + +/// Error returned by [`IcmpSocket::bind`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + /// The socket was already open. + InvalidState, + /// The endpoint isn't specified + InvalidEndpoint, + /// No route to host. + NoRoute, +} + +/// Error returned by [`IcmpSocket::send_to`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + /// No route to host. + NoRoute, + /// Socket not bound to an outgoing port. + SocketNotBound, + /// There is not enough transmit buffer capacity to ever send this packet. + PacketTooLarge, +} + +/// Error returned by [`IcmpSocket::recv_from`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + /// Provided buffer was smaller than the received packet. + Truncated, +} + +/// An ICMP socket. +pub struct IcmpSocket<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'a> IcmpSocket<'a> { + /// Create a new ICMP socket using the provided stack and buffers. + pub fn new( + stack: Stack<'a>, + rx_meta: &'a mut [PacketMetadata], + rx_buffer: &'a mut [u8], + tx_meta: &'a mut [PacketMetadata], + tx_buffer: &'a mut [u8], + ) -> Self { + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(icmp::Socket::new( + icmp::PacketBuffer::new(rx_meta, rx_buffer), + icmp::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); + + Self { stack, handle } + } + + /// Bind the socket to the given endpoint. + pub fn bind(&mut self, endpoint: T) -> Result<(), BindError> + where + T: Into, + { + let endpoint = endpoint.into(); + + if !endpoint.is_specified() { + return Err(BindError::InvalidEndpoint); + } + + match self.with_mut(|s, _| s.bind(endpoint)) { + Ok(()) => Ok(()), + Err(icmp::BindError::InvalidState) => Err(BindError::InvalidState), + Err(icmp::BindError::Unaddressable) => Err(BindError::NoRoute), + } + } + + fn with(&self, f: impl FnOnce(&icmp::Socket, &Interface) -> R) -> R { + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) + } + + fn with_mut(&self, f: impl FnOnce(&mut icmp::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub fn wait_recv_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_recv_ready(cx)) + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + /// + /// Returns the number of bytes received and the remote endpoint. + pub fn recv_from<'s>( + &'s self, + buf: &'s mut [u8], + ) -> impl Future> + 's { + poll_fn(|cx| self.poll_recv_from(buf, cx)) + } + + /// Receive a datagram. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready` with the + /// number of bytes received and the remote endpoint. + pub fn poll_recv_from(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll> { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok((n, meta)) => Poll::Ready(Ok((n, meta))), + // No data ready + Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + Err(icmp::RecvError::Exhausted) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Dequeue a packet received from a remote endpoint and calls the provided function with the + /// slice of the packet and the remote endpoint address and returns `Poll::Ready` with the + /// function's returned value. + /// + /// **Note**: when the size of the provided buffer is smaller than the size of the payload, + /// the packet is dropped and a `RecvError::Truncated` error is returned. + pub async fn recv_from_with(&self, f: F) -> Result + where + F: FnOnce((&[u8], IpAddress)) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| match s.recv() { + Ok(x) => Poll::Ready(Ok(unwrap!(f.take())(x))), + Err(icmp::RecvError::Exhausted) => { + cx.waker().wake_by_ref(); + Poll::Pending + } + Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + }) + }) + .await + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub fn wait_send_ready(&self) -> impl Future + '_ { + poll_fn(|cx| self.poll_send_ready(cx)) + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Send a datagram to the specified remote endpoint. + /// + /// This method will wait until the datagram has been sent. + /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Err(SendError::PacketTooLarge)` + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError> + where + T: Into, + { + let remote_endpoint: IpAddress = remote_endpoint.into(); + poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await + } + + /// Send a datagram to the specified remote endpoint. + /// + /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`. + /// + /// When the socket's send buffer is full, this method will return `Poll::Pending` + /// and register the current task to be notified when the buffer has space available. + /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Poll::Ready(Err(SendError::PacketTooLarge))` + /// + /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`. + pub fn poll_send_to(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll> + where + T: Into, + { + // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < buf.len()); + if send_capacity_too_small { + return Poll::Ready(Err(SendError::PacketTooLarge)); + } + + self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint.into()) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(Ok(())), + Err(icmp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(icmp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.is_open() { + Poll::Ready(Err(SendError::NoRoute)) + } else { + Poll::Ready(Err(SendError::SocketNotBound)) + } + } + }) + } + + /// Enqueue a packet to be sent to a given remote address with a zero-copy function. + /// + /// This method will wait until the buffer can fit the requested size before + /// calling the function to fill its contents. + pub async fn send_to_with(&mut self, size: usize, remote_endpoint: T, f: F) -> Result + where + T: Into, + F: FnOnce(&mut [u8]) -> R, + { + // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < size); + if send_capacity_too_small { + return Err(SendError::PacketTooLarge); + } + + let mut f = Some(f); + let remote_endpoint = remote_endpoint.into(); + poll_fn(move |cx| { + self.with_mut(|s, _| match s.send(size, remote_endpoint) { + Ok(buf) => Poll::Ready(Ok({ unwrap!(f.take())(buf) })), + Err(icmp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(icmp::SendError::Unaddressable) => Poll::Ready(Err(SendError::NoRoute)), + }) + }) + .await + } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub fn flush(&mut self) -> impl Future + '_ { + poll_fn(|cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + } + + /// Check whether the socket is open. + pub fn is_open(&self) -> bool { + self.with(|s, _| s.is_open()) + } + + /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet. + pub fn may_send(&self) -> bool { + self.with(|s, _| s.can_send()) + } + + /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer. + pub fn may_recv(&self) -> bool { + self.with(|s, _| s.can_recv()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_recv_capacity(&self) -> usize { + self.with(|s, _| s.packet_recv_capacity()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_send_capacity(&self) -> usize { + self.with(|s, _| s.packet_send_capacity()) + } + + /// Return the maximum number of bytes inside the recv buffer. + pub fn payload_recv_capacity(&self) -> usize { + self.with(|s, _| s.payload_recv_capacity()) + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn payload_send_capacity(&self) -> usize { + self.with(|s, _| s.payload_send_capacity()) + } + + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + pub fn hop_limit(&self) -> Option { + self.with(|s, _| s.hop_limit()) + } + + /// Set the hop limit field in the IP header of sent packets. + pub fn set_hop_limit(&mut self, hop_limit: Option) { + self.with_mut(|s, _| s.set_hop_limit(hop_limit)) + } +} + +impl Drop for IcmpSocket<'_> { + fn drop(&mut self) { + self.stack.with_mut(|i| i.sockets.remove(self.handle)); + } +} + +pub mod ping { + //! Ping utilities. + //! + //! This module allows for an easy ICMP Echo message interface used to + //! ping devices with an [ICMP Socket](IcmpSocket). + //! + //! ## Usage + //! + //! ``` + //! use core::net::Ipv4Addr; + //! use core::str::FromStr; + //! + //! use embassy_net::icmp::ping::{PingManager, PingParams}; + //! use embassy_net::icmp::PacketMetadata; + //! + //! let mut rx_buffer = [0; 256]; + //! let mut tx_buffer = [0; 256]; + //! let mut rx_meta = [PacketMetadata::EMPTY]; + //! let mut tx_meta = [PacketMetadata::EMPTY]; + //! + //! let mut ping_manager = PingManager::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + //! let addr = "192.168.8.1"; + //! let mut ping_params = PingParams::new(Ipv4Addr::from_str(addr).unwrap()); + //! ping_params.set_payload(b"Hello, router!"); + //! match ping_manager.ping(&ping_params).await { + //! Ok(time) => info!("Ping time of {}: {}ms", addr, time.as_millis()), + //! Err(ping_error) => warn!("{:?}", ping_error), + //! }; + //! ``` + + use core::net::IpAddr; + #[cfg(feature = "proto-ipv6")] + use core::net::Ipv6Addr; + + use embassy_time::{Duration, Instant, Timer, WithTimeout}; + #[cfg(feature = "proto-ipv6")] + use smoltcp::wire::IpAddress; + #[cfg(feature = "proto-ipv6")] + use smoltcp::wire::Ipv6Address; + + use super::*; + + /// Error returned by [`ping()`](PingManager::ping). + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PingError { + /// The target did not respond. + /// + /// The packet was sent but the Reply packet has not been recieved + /// in the timeout set by [`set_timeout()`](PingParams::set_timeout). + DestinationHostUnreachable, + /// The target has not been specified. + InvalidTargetAddress, + /// The source has not been specified (Ipv6 only). + #[cfg(feature = "proto-ipv6")] + InvalidSourceAddress, + /// The socket could not queue the packet in the buffer. + SocketSendTimeout, + /// Container error for [`icmp::BindError`]. + SocketBindError(BindError), + /// Container error for [`icmp::SendError`]. + SocketSendError(SendError), + /// Container error for [`icmp::RecvError`]. + SocketRecvError(RecvError), + } + + /// Manages ICMP ping operations. + /// + /// This struct provides functionality to send ICMP echo requests (pings) to a specified target + /// and measure the round-trip time for the requests. It supports both IPv4 and IPv6, depending + /// on the enabled features. + /// + /// # Fields + /// + /// * `stack` - The network stack instance used for managing network operations. + /// * `rx_meta` - Metadata buffer for receiving packets. + /// * `rx_buffer` - Buffer for receiving packets. + /// * `tx_meta` - Metadata buffer for transmitting packets. + /// * `tx_buffer` - Buffer for transmitting packets. + /// * `ident` - Identifier for the ICMP echo requests. + /// + /// # Methods + /// + /// * [`new`](PingManager::new) - Creates a new instance of `PingManager` with the specified stack and buffers. + /// * [`ping`](PingManager::ping) - Sends ICMP echo requests to the specified target and returns the average round-trip time. + pub struct PingManager<'d> { + stack: Stack<'d>, + rx_meta: &'d mut [PacketMetadata], + rx_buffer: &'d mut [u8], + tx_meta: &'d mut [PacketMetadata], + tx_buffer: &'d mut [u8], + ident: u16, + } + + impl<'d> PingManager<'d> { + /// Creates a new instance of [`PingManager`] with a [`Stack`] instance + /// and the buffers used for RX and TX. + /// + /// **note**: This does not yet creates the ICMP socket. + pub fn new( + stack: Stack<'d>, + rx_meta: &'d mut [PacketMetadata], + rx_buffer: &'d mut [u8], + tx_meta: &'d mut [PacketMetadata], + tx_buffer: &'d mut [u8], + ) -> Self { + Self { + stack, + rx_meta, + rx_buffer, + tx_meta, + tx_buffer, + ident: 0, + } + } + + /// Sends ICMP echo requests to the specified target and returns the average round-trip time. + /// + /// # Arguments + /// + /// * `params` - Parameters for configuring the ping operation. + /// + /// # Returns + /// + /// * `Ok(Duration)` - The average round-trip time for the ping requests. + /// * `Err(PingError)` - An error occurred during the ping operation. + pub async fn ping<'a>(&mut self, params: &PingParams<'a>) -> Result { + // Input validation + if params.target().is_none() { + return Err(PingError::InvalidTargetAddress); + } + #[cfg(feature = "proto-ipv6")] + if params.target().unwrap().is_ipv6() && params.source().is_none() { + return Err(PingError::InvalidSourceAddress); + } + // Increment the ident (wrapping u16) to respect standards + self.ident = self.ident.wrapping_add(1u16); + // Used to calculate the average duration + let mut total_duration = Duration::default(); + let mut num_of_durations = 0u16; + // Increment the sequence number as per standards + for seq_no in 0..params.count() { + // Make sure each ping takes at least 1 second to respect standards + let rate_limit_start = Instant::now(); + + // make a single ping + // - shorts out errors + // - select the ip version + let ping_duration = match params.target.unwrap() { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(_) => self.single_ping_v4(params, seq_no).await?, + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(_) => self.single_ping_v6(params, seq_no).await?, + }; + + // safely add up the durations of each ping + if let Some(dur) = total_duration.checked_add(ping_duration) { + total_duration = dur; + num_of_durations += 1; + } + + // 1 sec min per ping + let rate_limit_end = rate_limit_start.elapsed(); + if rate_limit_end <= params.rate_limit { + Timer::after(params.rate_limit.checked_sub(rate_limit_end).unwrap()).await; + } + } + // calculate and return the average duration + Ok(total_duration.checked_div(num_of_durations as u32).unwrap()) + } + + #[cfg(feature = "proto-ipv4")] + fn create_repr_ipv4<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv4Repr<'b> { + Icmpv4Repr::EchoRequest { + ident: self.ident, + seq_no, + data: params.payload, + } + } + + #[cfg(feature = "proto-ipv6")] + fn create_repr_ipv6<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv6Repr<'b> { + Icmpv6Repr::EchoRequest { + ident: self.ident, + seq_no, + data: params.payload, + } + } + + #[cfg(feature = "proto-ipv4")] + async fn single_ping_v4(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result { + let ping_repr = self.create_repr_ipv4(params, seq_no); + + // Create the socket and set hop limit and bind it to the endpoint with the ident + let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer); + socket.set_hop_limit(params.hop_limit); + if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) { + return Err(PingError::SocketBindError(e)); + } + + // Helper func to fill the buffer when sending the ICMP packet + fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv4Repr<'_>) -> Instant { + let mut icmp_packet = Icmpv4Packet::new_unchecked(buf); + ping_repr.emit(&mut icmp_packet, &ChecksumCapabilities::default()); + Instant::now() + } + + // Send with timeout the ICMP packet filling it with the helper function + let send_result = socket + .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| { + fill_packet_buffer(buf, ping_repr) + }) + .with_timeout(Duration::from_millis(100)) + .await; + // Filter and translate potential errors from sending the packet + let now = match send_result { + Ok(send_result) => match send_result { + Ok(i) => i, + Err(e) => return Err(PingError::SocketSendError(e)), + }, + Err(_) => return Err(PingError::SocketSendTimeout), + }; + + // Helper function for the recieve helper function to validate the echo reply + fn filter_pong(buf: &[u8], seq_no: u16) -> bool { + let pong_packet = match Icmpv4Packet::new_checked(buf) { + Ok(pak) => pak, + Err(_) => return false, + }; + pong_packet.echo_seq_no() == seq_no + } + + // Helper function to recieve and return the correct echo reply when it finds it + async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { + while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await { + Ok(b) => !b, + Err(e) => return Err(PingError::SocketRecvError(e)), + } {} + Ok(()) + } + + // Calls the recieve helper function with a timeout + match recv_pong(&socket, seq_no).with_timeout(params.timeout).await { + Ok(res) => res?, + Err(_) => return Err(PingError::DestinationHostUnreachable), + } + + // Return the round trip duration + Ok(now.elapsed()) + } + + #[cfg(feature = "proto-ipv6")] + async fn single_ping_v6(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result { + let ping_repr = self.create_repr_ipv6(params, seq_no); + + // Create the socket and set hop limit and bind it to the endpoint with the ident + let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer); + socket.set_hop_limit(params.hop_limit); + if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) { + return Err(PingError::SocketBindError(e)); + } + + // Helper func to fill the buffer when sending the ICMP packet + fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv6Repr<'_>, params: &PingParams<'_>) -> Instant { + let mut icmp_packet = Icmpv6Packet::new_unchecked(buf); + let target = match params.target().unwrap() { + IpAddr::V4(_) => unreachable!(), + IpAddr::V6(addr) => addr, + }; + ping_repr.emit( + ¶ms.source().unwrap(), + &target, + &mut icmp_packet, + &ChecksumCapabilities::default(), + ); + Instant::now() + } + + // Send with timeout the ICMP packet filling it with the helper function + let send_result = socket + .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| { + fill_packet_buffer(buf, ping_repr, params) + }) + .with_timeout(Duration::from_millis(100)) + .await; + let now = match send_result { + Ok(send_result) => match send_result { + Ok(i) => i, + Err(e) => return Err(PingError::SocketSendError(e)), + }, + Err(_) => return Err(PingError::SocketSendTimeout), + }; + + // Helper function for the recieve helper function to validate the echo reply + fn filter_pong(buf: &[u8], seq_no: u16) -> bool { + let pong_packet = match Icmpv6Packet::new_checked(buf) { + Ok(pak) => pak, + Err(_) => return false, + }; + pong_packet.echo_seq_no() == seq_no + } + + // Helper function to recieve and return the correct echo reply when it finds it + async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { + while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await { + Ok(b) => !b, + Err(e) => return Err(PingError::SocketRecvError(e)), + } {} + Ok(()) + } + + // Calls the recieve helper function with a timeout + match recv_pong(&socket, seq_no).with_timeout(params.timeout).await { + Ok(res) => res?, + Err(_) => return Err(PingError::DestinationHostUnreachable), + } + + // Return the round trip duration + Ok(now.elapsed()) + } + } + + /// Parameters for configuring the ping operation. + /// + /// This struct provides various configuration options for performing ICMP ping operations, + /// including the target IP address, payload data, hop limit, number of pings, and timeout duration. + /// + /// # Fields + /// + /// * `target` - The target IP address for the ping operation. + /// * `source` - The source IP address for the ping operation (IPv6 only). + /// * `payload` - The data to be sent in the payload field of the ping. + /// * `hop_limit` - The hop limit to be used by the socket. + /// * `count` - The number of pings to be sent in one ping operation. + /// * `timeout` - The timeout duration before returning a [`PingError::DestinationHostUnreachable`] error. + /// * `rate_limit` - The minimum time per echo request. + pub struct PingParams<'a> { + target: Option, + #[cfg(feature = "proto-ipv6")] + source: Option, + payload: &'a [u8], + hop_limit: Option, + count: u16, + timeout: Duration, + rate_limit: Duration, + } + + impl Default for PingParams<'_> { + fn default() -> Self { + Self { + target: None, + #[cfg(feature = "proto-ipv6")] + source: None, + payload: b"embassy-net", + hop_limit: None, + count: 4, + timeout: Duration::from_secs(4), + rate_limit: Duration::from_secs(1), + } + } + } + + impl<'a> PingParams<'a> { + /// Creates a new instance of [`PingParams`] with the specified target IP address. + pub fn new>(target: T) -> Self { + Self { + target: Some(PingParams::ip_addr_to_smoltcp(target)), + #[cfg(feature = "proto-ipv6")] + source: None, + payload: b"embassy-net", + hop_limit: None, + count: 4, + timeout: Duration::from_secs(4), + rate_limit: Duration::from_secs(1), + } + } + + fn ip_addr_to_smoltcp>(ip_addr: T) -> IpAddress { + match ip_addr.into() { + #[cfg(feature = "proto-ipv4")] + IpAddr::V4(v4) => IpAddress::Ipv4(v4), + #[cfg(not(feature = "proto-ipv4"))] + IpAddr::V4(_) => unreachable!(), + #[cfg(feature = "proto-ipv6")] + IpAddr::V6(v6) => IpAddress::Ipv6(v6), + #[cfg(not(feature = "proto-ipv6"))] + IpAddr::V6(_) => unreachable!(), + } + } + + /// Sets the target IP address for the ping. + pub fn set_target>(&mut self, target: T) -> &mut Self { + self.target = Some(PingParams::ip_addr_to_smoltcp(target)); + self + } + + /// Retrieves the target IP address for the ping. + pub fn target(&self) -> Option { + self.target.map(|t| t.into()) + } + + /// Sets the source IP address for the ping (IPv6 only). + #[cfg(feature = "proto-ipv6")] + pub fn set_source>(&mut self, source: T) -> &mut Self { + self.source = Some(source.into()); + self + } + + /// Retrieves the source IP address for the ping (IPv6 only). + #[cfg(feature = "proto-ipv6")] + pub fn source(&self) -> Option { + self.source + } + + /// Sets the data used in the payload field of the ping with the provided slice. + pub fn set_payload(&mut self, payload: &'a [u8]) -> &mut Self { + self.payload = payload; + self + } + + /// Gives a reference to the slice of data that's going to be sent in the payload field + /// of the ping. + pub fn payload(&self) -> &'a [u8] { + self.payload + } + + /// Sets the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit). + /// + /// **Note**: A hop limit of [`Some(0)`](Some()) is equivalent to a hop limit of [`None`]. + pub fn set_hop_limit(&mut self, hop_limit: Option) -> &mut Self { + let mut hop_limit = hop_limit; + if hop_limit.is_some_and(|x| x == 0) { + hop_limit = None + } + self.hop_limit = hop_limit; + self + } + + /// Retrieves the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit). + pub fn hop_limit(&self) -> Option { + self.hop_limit + } + + /// Sets the count used for specifying the number of pings done on one + /// [`ping()`](PingManager::ping) call. + /// + /// **Note**: A count of 0 will be set as 1. + pub fn set_count(&mut self, count: u16) -> &mut Self { + let mut count = count; + if count == 0 { + count = 1; + } + self.count = count; + self + } + + /// Retrieve the count used for specifying the number of pings done on one + /// [`ping()`](PingManager::ping) call. + pub fn count(&self) -> u16 { + self.count + } + + /// Sets the timeout used before returning [`PingError::DestinationHostUnreachable`] + /// when waiting for the Echo Reply icmp packet. + pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self { + self.timeout = timeout; + self + } + + /// Retrieve the timeout used before returning [`PingError::DestinationHostUnreachable`] + /// when waiting for the Echo Reply icmp packet. + pub fn timeout(&self) -> Duration { + self.timeout + } + + /// Sets the `rate_limit`: minimum time per echo request. + pub fn set_rate_limit(&mut self, rate_limit: Duration) -> &mut Self { + self.rate_limit = rate_limit; + self + } + + /// Retrieve the rate_limit. + pub fn rate_limit(&self) -> Duration { + self.rate_limit + } + } +} diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 56321cec9..693a39ed5 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -1,4 +1,4 @@ -#![cfg_attr(not(feature = "std"), no_std)] +#![no_std] #![allow(async_fn_in_trait)] #![warn(missing_docs)] #![doc = include_str!("../README.md")] @@ -12,9 +12,11 @@ compile_error!("You must enable at least one of the following features: proto-ip // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -mod device; #[cfg(feature = "dns")] pub mod dns; +mod driver_util; +#[cfg(feature = "icmp")] +pub mod icmp; #[cfg(feature = "raw")] pub mod raw; #[cfg(feature = "tcp")] @@ -25,6 +27,7 @@ pub mod udp; use core::cell::RefCell; use core::future::{poll_fn, Future}; +use core::mem::MaybeUninit; use core::pin::pin; use core::task::{Context, Poll}; @@ -32,14 +35,14 @@ pub use embassy_net_driver as driver; use embassy_net_driver::{Driver, LinkState}; use embassy_sync::waitqueue::WakerRegistration; use embassy_time::{Instant, Timer}; -#[allow(unused_imports)] use heapless::Vec; #[cfg(feature = "dns")] pub use smoltcp::config::DNS_MAX_SERVER_COUNT; -#[cfg(feature = "igmp")] +#[cfg(feature = "multicast")] pub use smoltcp::iface::MulticastError; -#[allow(unused_imports)] -use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; +#[cfg(any(feature = "dns", feature = "dhcpv4"))] +use smoltcp::iface::SocketHandle; +use smoltcp::iface::{Interface, SocketSet, SocketStorage}; use smoltcp::phy::Medium; #[cfg(feature = "dhcpv4")] use smoltcp::socket::dhcpv4::{self, RetryConfig}; @@ -57,7 +60,7 @@ pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; #[cfg(feature = "proto-ipv6")] pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; -use crate::device::DriverAdapter; +use crate::driver_util::DriverAdapter; use crate::time::{instant_from_smoltcp, instant_to_smoltcp}; const LOCAL_PORT_MIN: u16 = 1025; @@ -69,33 +72,33 @@ const MAX_HOSTNAME_LEN: usize = 32; /// Memory resources needed for a network stack. pub struct StackResources { - sockets: [SocketStorage<'static>; SOCK], + sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>, + inner: MaybeUninit>, #[cfg(feature = "dns")] - queries: [Option; MAX_QUERIES], + queries: MaybeUninit<[Option; MAX_QUERIES]>, #[cfg(feature = "dhcpv4-hostname")] - hostname: core::cell::UnsafeCell, + hostname: HostnameResources, } #[cfg(feature = "dhcpv4-hostname")] struct HostnameResources { - option: smoltcp::wire::DhcpOption<'static>, - data: [u8; MAX_HOSTNAME_LEN], + option: MaybeUninit>, + data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>, } impl StackResources { /// Create a new set of stack resources. pub const fn new() -> Self { - #[cfg(feature = "dns")] - const INIT: Option = None; Self { - sockets: [SocketStorage::EMPTY; SOCK], + sockets: MaybeUninit::uninit(), + inner: MaybeUninit::uninit(), #[cfg(feature = "dns")] - queries: [INIT; MAX_QUERIES], + queries: MaybeUninit::uninit(), #[cfg(feature = "dhcpv4-hostname")] - hostname: core::cell::UnsafeCell::new(HostnameResources { - option: smoltcp::wire::DhcpOption { kind: 0, data: &[] }, - data: [0; MAX_HOSTNAME_LEN], - }), + hostname: HostnameResources { + option: MaybeUninit::uninit(), + data: MaybeUninit::uninit(), + }, } } } @@ -179,7 +182,7 @@ pub struct Config { impl Config { /// IPv4 configuration with static addressing. #[cfg(feature = "proto-ipv4")] - pub fn ipv4_static(config: StaticConfigV4) -> Self { + pub const fn ipv4_static(config: StaticConfigV4) -> Self { Self { ipv4: ConfigV4::Static(config), #[cfg(feature = "proto-ipv6")] @@ -189,7 +192,7 @@ impl Config { /// IPv6 configuration with static addressing. #[cfg(feature = "proto-ipv6")] - pub fn ipv6_static(config: StaticConfigV6) -> Self { + pub const fn ipv6_static(config: StaticConfigV6) -> Self { Self { #[cfg(feature = "proto-ipv4")] ipv4: ConfigV4::None, @@ -205,7 +208,7 @@ impl Config { /// let _cfg = Config::dhcpv4(Default::default()); /// ``` #[cfg(feature = "dhcpv4")] - pub fn dhcpv4(config: DhcpConfig) -> Self { + pub const fn dhcpv4(config: DhcpConfig) -> Self { Self { ipv4: ConfigV4::Dhcp(config), #[cfg(feature = "proto-ipv6")] @@ -239,16 +242,32 @@ pub enum ConfigV6 { Static(StaticConfigV6), } -/// A network stack. +/// Network stack runner. /// -/// This is the main entry point for the network stack. -pub struct Stack { - pub(crate) socket: RefCell, - inner: RefCell>, +/// You must call [`Runner::run()`] in a background task for the network stack to work. +pub struct Runner<'d, D: Driver> { + driver: D, + stack: Stack<'d>, } -struct Inner { - device: D, +/// Network stack handle +/// +/// Use this to create sockets. It's `Copy`, so you can pass +/// it by value instead of by reference. +#[derive(Copy, Clone)] +pub struct Stack<'d> { + inner: &'d RefCell, +} + +pub(crate) struct Inner { + pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased. + pub(crate) iface: Interface, + /// Waker used for triggering polls. + pub(crate) waker: WakerRegistration, + /// Waker used for waiting for link up or config up. + state_waker: WakerRegistration, + hardware_address: HardwareAddress, + next_local_port: u16, link_up: bool, #[cfg(feature = "proto-ipv4")] static_v4: Option, @@ -256,20 +275,88 @@ struct Inner { static_v6: Option, #[cfg(feature = "dhcpv4")] dhcp_socket: Option, - config_waker: WakerRegistration, #[cfg(feature = "dns")] dns_socket: SocketHandle, #[cfg(feature = "dns")] dns_waker: WakerRegistration, #[cfg(feature = "dhcpv4-hostname")] - hostname: &'static mut core::cell::UnsafeCell, + hostname: *mut HostnameResources, } -pub(crate) struct SocketStack { - pub(crate) sockets: SocketSet<'static>, - pub(crate) iface: Interface, - pub(crate) waker: WakerRegistration, - next_local_port: u16, +fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> { + x +} + +/// Create a new network stack. +pub fn new<'d, D: Driver, const SOCK: usize>( + mut driver: D, + config: Config, + resources: &'d mut StackResources, + random_seed: u64, +) -> (Stack<'d>, Runner<'d, D>) { + let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address()); + let mut iface_cfg = smoltcp::iface::Config::new(hardware_address); + iface_cfg.random_seed = random_seed; + + let iface = Interface::new( + iface_cfg, + &mut DriverAdapter { + inner: &mut driver, + cx: None, + medium, + }, + instant_to_smoltcp(Instant::now()), + ); + + unsafe fn transmute_slice(x: &mut [T]) -> &'static mut [T] { + core::mem::transmute(x) + } + + let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]); + #[allow(unused_mut)] + let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) }); + + let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; + + #[cfg(feature = "dns")] + let dns_socket = sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(unsafe { + transmute_slice(resources.queries.write([const { None }; MAX_QUERIES])) + }), + )); + + let mut inner = Inner { + sockets, + iface, + waker: WakerRegistration::new(), + state_waker: WakerRegistration::new(), + next_local_port, + hardware_address, + link_up: false, + #[cfg(feature = "proto-ipv4")] + static_v4: None, + #[cfg(feature = "proto-ipv6")] + static_v6: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), + #[cfg(feature = "dhcpv4-hostname")] + hostname: &mut resources.hostname, + }; + + #[cfg(feature = "proto-ipv4")] + inner.set_config_v4(config.ipv4); + #[cfg(feature = "proto-ipv6")] + inner.set_config_v6(config.ipv6); + inner.apply_static_config(); + + let inner = &*resources.inner.write(RefCell::new(inner)); + let stack = Stack { inner }; + (stack, Runner { driver, stack }) } fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) { @@ -292,92 +379,26 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddres } } -impl Stack { - /// Create a new network stack. - pub fn new( - mut device: D, - config: Config, - resources: &'static mut StackResources, - random_seed: u64, - ) -> Self { - let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address()); - let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr); - iface_cfg.random_seed = random_seed; - - let iface = Interface::new( - iface_cfg, - &mut DriverAdapter { - inner: &mut device, - cx: None, - medium, - }, - instant_to_smoltcp(Instant::now()), - ); - - let sockets = SocketSet::new(&mut resources.sockets[..]); - - let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; - - #[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))] - let mut socket = SocketStack { - sockets, - iface, - waker: WakerRegistration::new(), - next_local_port, - }; - - let mut inner = Inner { - device, - link_up: false, - #[cfg(feature = "proto-ipv4")] - static_v4: None, - #[cfg(feature = "proto-ipv6")] - static_v6: None, - #[cfg(feature = "dhcpv4")] - dhcp_socket: None, - config_waker: WakerRegistration::new(), - #[cfg(feature = "dns")] - dns_socket: socket.sockets.add(dns::Socket::new( - &[], - managed::ManagedSlice::Borrowed(&mut resources.queries), - )), - #[cfg(feature = "dns")] - dns_waker: WakerRegistration::new(), - #[cfg(feature = "dhcpv4-hostname")] - hostname: &mut resources.hostname, - }; - - #[cfg(feature = "proto-ipv4")] - inner.set_config_v4(&mut socket, config.ipv4); - #[cfg(feature = "proto-ipv6")] - inner.set_config_v6(&mut socket, config.ipv6); - inner.apply_static_config(&mut socket); - - Self { - socket: RefCell::new(socket), - inner: RefCell::new(inner), - } +impl<'d> Stack<'d> { + fn with(&self, f: impl FnOnce(&Inner) -> R) -> R { + f(&self.inner.borrow()) } - fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { - f(&*self.socket.borrow(), &*self.inner.borrow()) - } - - fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { - f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) + fn with_mut(&self, f: impl FnOnce(&mut Inner) -> R) -> R { + f(&mut self.inner.borrow_mut()) } /// Get the hardware address of the network interface. pub fn hardware_address(&self) -> HardwareAddress { - self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()).0) + self.with(|i| i.hardware_address) } - /// Get whether the link is up. + /// Check whether the link is up. pub fn is_link_up(&self) -> bool { - self.with(|_s, i| i.link_up) + self.with(|i| i.link_up) } - /// Get whether the network stack has a valid IP configuration. + /// Check whether the network stack has a valid IP configuration. /// This is true if the network stack has a static IP configuration or if DHCP has completed pub fn is_config_up(&self) -> bool { let v4_up; @@ -404,10 +425,20 @@ impl Stack { v4_up || v6_up } + /// Wait for the network device to obtain a link signal. + pub async fn wait_link_up(&self) { + self.wait(|| self.is_link_up()).await + } + + /// Wait for the network device to lose link signal. + pub async fn wait_link_down(&self) { + self.wait(|| !self.is_link_up()).await + } + /// Wait for the network stack to obtain a valid IP configuration. /// /// ## Notes: - /// - Ensure [`Stack::run`] has been called before using this function. + /// - Ensure [`Runner::run`] has been started before using this function. /// /// - This function may never return (e.g. if no configuration is obtained through DHCP). /// The caller is supposed to handle a timeout for this case. @@ -420,42 +451,44 @@ impl Stack { /// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP). /// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it. /// static RESOURCES: StaticCell> = StaticCell::new(); - /// static STACK: StaticCell = StaticCell::new(); - /// let stack = &*STACK.init(embassy_net::Stack::new( - /// device, + /// let (stack, runner) = embassy_net::new( + /// driver, /// config, /// RESOURCES.init(embassy_net::StackResources::new()), /// seed - /// )); - /// // Launch network task that runs `stack.run().await` - /// spawner.spawn(net_task(stack)).unwrap(); + /// ); + /// // Launch network task that runs `runner.run().await` + /// spawner.spawn(net_task(runner)).unwrap(); /// // Wait for DHCP config /// stack.wait_config_up().await; /// // use the network stack /// // ... /// ``` pub async fn wait_config_up(&self) { - // If the config is up already, we can return immediately. - if self.is_config_up() { - return; - } + self.wait(|| self.is_config_up()).await + } - poll_fn(|cx| { - if self.is_config_up() { + /// Wait for the network stack to lose a valid IP configuration. + pub async fn wait_config_down(&self) { + self.wait(|| !self.is_config_up()).await + } + + fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future + 'a { + poll_fn(move |cx| { + if predicate() { Poll::Ready(()) } else { // If the config is not up, we register a waker that is woken up // when a config is applied (static or DHCP). trace!("Waiting for config up"); - self.with_mut(|_, i| { - i.config_waker.register(cx.waker()); + self.with_mut(|i| { + i.state_waker.register(cx.waker()); }); Poll::Pending } }) - .await; } /// Get the current IPv4 configuration. @@ -464,45 +497,33 @@ impl Stack { /// acquire an IP address, or Some if it has. #[cfg(feature = "proto-ipv4")] pub fn config_v4(&self) -> Option { - self.with(|_, i| i.static_v4.clone()) + self.with(|i| i.static_v4.clone()) } /// Get the current IPv6 configuration. #[cfg(feature = "proto-ipv6")] pub fn config_v6(&self) -> Option { - self.with(|_, i| i.static_v6.clone()) + self.with(|i| i.static_v6.clone()) } /// Set the IPv4 configuration. #[cfg(feature = "proto-ipv4")] pub fn set_config_v4(&self, config: ConfigV4) { - self.with_mut(|s, i| { - i.set_config_v4(s, config); - i.apply_static_config(s); + self.with_mut(|i| { + i.set_config_v4(config); + i.apply_static_config(); }) } /// Set the IPv6 configuration. #[cfg(feature = "proto-ipv6")] pub fn set_config_v6(&self, config: ConfigV6) { - self.with_mut(|s, i| { - i.set_config_v6(s, config); - i.apply_static_config(s); + self.with_mut(|i| { + i.set_config_v6(config); + i.apply_static_config(); }) } - /// Run the network stack. - /// - /// You must call this in a background task, to process network events. - pub async fn run(&self) -> ! { - poll_fn(|cx| { - self.with_mut(|s, i| i.poll(cx, s)); - Poll::<()>::Pending - }) - .await; - unreachable!() - } - /// Make a query for a given name and return the corresponding IP addresses. #[cfg(feature = "dns")] pub async fn dns_query( @@ -528,11 +549,11 @@ impl Stack { } let query = poll_fn(|cx| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); - match socket.start_query(s.iface.context(), name, qtype) { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + match socket.start_query(i.iface.context(), name, qtype) { Ok(handle) => { - s.waker.wake(); + i.waker.wake(); Poll::Ready(Ok(handle)) } Err(dns::StartQueryError::NoFreeSlot) => { @@ -569,17 +590,17 @@ impl Stack { } let drop = OnDrop::new(|| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); socket.cancel_query(query); - s.waker.wake(); + i.waker.wake(); i.dns_waker.wake(); }) }); let res = poll_fn(|cx| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); match socket.get_query_result(query) { Ok(addrs) => { i.dns_waker.wake(); @@ -604,104 +625,34 @@ impl Stack { } } -#[cfg(feature = "igmp")] -impl Stack { +#[cfg(feature = "multicast")] +impl<'d> Stack<'d> { /// Join a multicast group. - pub async fn join_multicast_group(&self, addr: T) -> Result - where - T: Into, - { - let addr = addr.into(); - - poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await - } - - /// Join a multicast group. - /// - /// When the send queue is full, this method will return `Poll::Pending` - /// and register the current task to be notified when the queue has space available. - pub fn poll_join_multicast_group(&self, addr: T, cx: &mut Context<'_>) -> Poll> - where - T: Into, - { - let addr = addr.into(); - - self.with_mut(|s, i| { - let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address()); - let mut smoldev = DriverAdapter { - cx: Some(cx), - inner: &mut i.device, - medium, - }; - - match s - .iface - .join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now())) - { - Ok(announce_sent) => Poll::Ready(Ok(announce_sent)), - Err(MulticastError::Exhausted) => Poll::Pending, - Err(other) => Poll::Ready(Err(other)), - } - }) + pub fn join_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.join_multicast_group(addr)) } /// Leave a multicast group. - pub async fn leave_multicast_group(&self, addr: T) -> Result - where - T: Into, - { - let addr = addr.into(); - - poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await - } - - /// Leave a multicast group. - /// - /// When the send queue is full, this method will return `Poll::Pending` - /// and register the current task to be notified when the queue has space available. - pub fn poll_leave_multicast_group(&self, addr: T, cx: &mut Context<'_>) -> Poll> - where - T: Into, - { - let addr = addr.into(); - - self.with_mut(|s, i| { - let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address()); - let mut smoldev = DriverAdapter { - cx: Some(cx), - inner: &mut i.device, - medium, - }; - - match s - .iface - .leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now())) - { - Ok(leave_sent) => Poll::Ready(Ok(leave_sent)), - Err(MulticastError::Exhausted) => Poll::Pending, - Err(other) => Poll::Ready(Err(other)), - } - }) + pub fn leave_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.leave_multicast_group(addr)) } /// Get whether the network stack has joined the given multicast group. - pub fn has_multicast_group>(&self, addr: T) -> bool { - self.socket.borrow().iface.has_multicast_group(addr) + pub fn has_multicast_group(&self, addr: impl Into) -> bool { + self.with(|i| i.iface.has_multicast_group(addr)) } } -impl SocketStack { - #[allow(clippy::absurd_extreme_comparisons, dead_code)] +impl Inner { + #[allow(clippy::absurd_extreme_comparisons)] pub fn get_local_port(&mut self) -> u16 { let res = self.next_local_port; self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; res } -} -impl Inner { #[cfg(feature = "proto-ipv4")] - pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) { + pub fn set_config_v4(&mut self, config: ConfigV4) { // Handle static config. self.static_v4 = match config.clone() { ConfigV4::None => None, @@ -717,12 +668,12 @@ impl Inner { // Create the socket if it doesn't exist. if self.dhcp_socket.is_none() { let socket = smoltcp::socket::dhcpv4::Socket::new(); - let handle = _s.sockets.add(socket); + let handle = self.sockets.add(socket); self.dhcp_socket = Some(handle); } // Configure it - let socket = _s.sockets.get_mut::(unwrap!(self.dhcp_socket)); + let socket = self.sockets.get_mut::(unwrap!(self.dhcp_socket)); socket.set_ignore_naks(c.ignore_naks); socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp)); socket.set_ports(c.server_port, c.client_port); @@ -731,19 +682,20 @@ impl Inner { socket.set_outgoing_options(&[]); #[cfg(feature = "dhcpv4-hostname")] if let Some(h) = c.hostname { - // safety: we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. - let hostname = unsafe { &mut *self.hostname.get() }; + // safety: + // - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. + // - we know this pointer lives for as long as the stack exists, because `new()` borrows + // the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp. + let hostname = unsafe { &mut *self.hostname }; // create data - // safety: we know the buffer lives forever, new borrows the StackResources for 'static. - // also we won't modify it until next call to this function. - hostname.data[..h.len()].copy_from_slice(h.as_bytes()); - let data: &[u8] = &hostname.data[..h.len()]; - let data: &'static [u8] = unsafe { core::mem::transmute(data) }; + let data = hostname.data.write([0; MAX_HOSTNAME_LEN]); + data[..h.len()].copy_from_slice(h.as_bytes()); + let data: &[u8] = &data[..h.len()]; // set the option. - hostname.option = smoltcp::wire::DhcpOption { data, kind: 12 }; - socket.set_outgoing_options(core::slice::from_ref(&hostname.option)); + let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 }); + socket.set_outgoing_options(core::slice::from_ref(option)); } socket.reset(); @@ -751,7 +703,7 @@ impl Inner { _ => { // Remove DHCP socket if any. if let Some(socket) = self.dhcp_socket { - _s.sockets.remove(socket); + self.sockets.remove(socket); self.dhcp_socket = None; } } @@ -759,14 +711,14 @@ impl Inner { } #[cfg(feature = "proto-ipv6")] - pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) { + pub fn set_config_v6(&mut self, config: ConfigV6) { self.static_v6 = match config { ConfigV6::None => None, ConfigV6::Static(c) => Some(c), }; } - fn apply_static_config(&mut self, s: &mut SocketStack) { + fn apply_static_config(&mut self) { let mut addrs = Vec::new(); #[cfg(feature = "dns")] let mut dns_servers: Vec<_, 6> = Vec::new(); @@ -782,7 +734,7 @@ impl Inner { debug!(" Default gateway: {:?}", config.gateway); unwrap!(addrs.push(IpCidr::Ipv4(config.address)).ok()); - gateway_v4 = config.gateway.into(); + gateway_v4 = config.gateway; #[cfg(feature = "dns")] for s in &config.dns_servers { debug!(" DNS server: {:?}", s); @@ -810,20 +762,20 @@ impl Inner { } // Apply addresses - s.iface.update_ip_addrs(|a| *a = addrs); + self.iface.update_ip_addrs(|a| *a = addrs); // Apply gateways #[cfg(feature = "proto-ipv4")] if let Some(gateway) = gateway_v4 { - unwrap!(s.iface.routes_mut().add_default_ipv4_route(gateway)); + unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway)); } else { - s.iface.routes_mut().remove_default_ipv4_route(); + self.iface.routes_mut().remove_default_ipv4_route(); } #[cfg(feature = "proto-ipv6")] if let Some(gateway) = gateway_v6 { - unwrap!(s.iface.routes_mut().add_default_ipv6_route(gateway)); + unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway)); } else { - s.iface.routes_mut().remove_default_ipv6_route(); + self.iface.routes_mut().remove_default_ipv6_route(); } // Apply DNS servers @@ -835,18 +787,18 @@ impl Inner { } else { dns_servers.len() }; - s.sockets + self.sockets .get_mut::(self.dns_socket) .update_servers(&dns_servers[..count]); } - self.config_waker.wake(); + self.state_waker.wake(); } - fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { - s.waker.register(cx.waker()); + fn poll(&mut self, cx: &mut Context<'_>, driver: &mut D) { + self.waker.register(cx.waker()); - let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address()); + let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address()); #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] { @@ -859,43 +811,41 @@ impl Inner { _ => false, }; if do_set { - s.iface.set_hardware_addr(_hardware_addr); + self.iface.set_hardware_addr(_hardware_addr); } } let timestamp = instant_to_smoltcp(Instant::now()); let mut smoldev = DriverAdapter { cx: Some(cx), - inner: &mut self.device, + inner: driver, medium, }; - s.iface.poll(timestamp, &mut smoldev, &mut s.sockets); + self.iface.poll(timestamp, &mut smoldev, &mut self.sockets); // Update link up let old_link_up = self.link_up; - self.link_up = self.device.link_state(cx) == LinkState::Up; + self.link_up = driver.link_state(cx) == LinkState::Up; // Print when changed if old_link_up != self.link_up { info!("link_up = {:?}", self.link_up); + self.state_waker.wake(); } - #[allow(unused_mut)] - let mut apply_config = false; - #[cfg(feature = "dhcpv4")] if let Some(dhcp_handle) = self.dhcp_socket { - let socket = s.sockets.get_mut::(dhcp_handle); + let socket = self.sockets.get_mut::(dhcp_handle); - if self.link_up { + let configure = if self.link_up { if old_link_up != self.link_up { socket.reset(); } match socket.poll() { - None => {} + None => false, Some(dhcpv4::Event::Deconfigured) => { self.static_v4 = None; - apply_config = true; + true } Some(dhcpv4::Event::Configured(config)) => { self.static_v4 = Some(StaticConfigV4 { @@ -903,21 +853,22 @@ impl Inner { gateway: config.router, dns_servers: config.dns_servers, }); - apply_config = true; + true } } } else if old_link_up { socket.reset(); self.static_v4 = None; - apply_config = true; + true + } else { + false + }; + if configure { + self.apply_static_config() } } - if apply_config { - self.apply_static_config(s); - } - - if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { + if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) { let t = pin!(Timer::at(instant_from_smoltcp(poll_at))); if t.poll(cx).is_ready() { cx.waker().wake_by_ref(); @@ -925,3 +876,17 @@ impl Inner { } } } + +impl<'d, D: Driver> Runner<'d, D> { + /// Run the network stack. + /// + /// You must call this in a background task, to process network events. + pub async fn run(&mut self) -> ! { + poll_fn(|cx| { + self.stack.with_mut(|i| i.poll(cx, &mut self.driver)); + Poll::<()>::Pending + }) + .await; + unreachable!() + } +} diff --git a/embassy-net/src/raw.rs b/embassy-net/src/raw.rs index 7ecd913e7..c9f753f13 100644 --- a/embassy-net/src/raw.rs +++ b/embassy-net/src/raw.rs @@ -1,7 +1,6 @@ //! Raw sockets. -use core::cell::RefCell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::mem; use core::task::{Context, Poll}; @@ -9,9 +8,9 @@ use embassy_net_driver::Driver; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::raw; pub use smoltcp::socket::raw::PacketMetadata; -use smoltcp::wire::{IpProtocol, IpVersion}; +pub use smoltcp::wire::{IpProtocol, IpVersion}; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by [`RawSocket::recv`] and [`RawSocket::send`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -23,14 +22,14 @@ pub enum RecvError { /// An Raw socket. pub struct RawSocket<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'a> RawSocket<'a> { /// Create a new Raw socket using the provided stack and buffers. pub fn new( - stack: &'a Stack, + stack: Stack<'a>, ip_version: IpVersion, ip_protocol: IpProtocol, rx_meta: &'a mut [PacketMetadata], @@ -38,31 +37,37 @@ impl<'a> RawSocket<'a> { tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - let s = &mut *stack.socket.borrow_mut(); + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(raw::Socket::new( + ip_version, + ip_protocol, + raw::PacketBuffer::new(rx_meta, rx_buffer), + raw::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); - let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(raw::Socket::new( - ip_version, - ip_protocol, - raw::PacketBuffer::new(rx_meta, rx_buffer), - raw::PacketBuffer::new(tx_meta, tx_buffer), - )); - - Self { - stack: &stack.socket, - handle, - } + Self { stack, handle } } fn with_mut(&self, f: impl FnOnce(&mut raw::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub fn wait_recv_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_recv_ready(cx)) } /// Receive a datagram. @@ -72,6 +77,24 @@ impl<'a> RawSocket<'a> { poll_fn(move |cx| self.poll_recv(buf, cx)).await } + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + /// Receive a datagram. /// /// When no datagram is available, this method will return `Poll::Pending` and @@ -88,11 +111,38 @@ impl<'a> RawSocket<'a> { }) } + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub fn wait_send_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_send_ready(cx)) + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + /// Send a datagram. /// /// This method will wait until the datagram has been sent.` - pub async fn send(&self, buf: &[u8]) { - poll_fn(move |cx| self.poll_send(buf, cx)).await + pub fn send<'s>(&'s self, buf: &'s [u8]) -> impl Future + 's { + poll_fn(|cx| self.poll_send(buf, cx)) } /// Send a datagram. @@ -111,10 +161,30 @@ impl<'a> RawSocket<'a> { } }) } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub fn flush(&mut self) -> impl Future + '_ { + poll_fn(|cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + } } impl Drop for RawSocket<'_> { fn drop(&mut self) { - self.stack.borrow_mut().sockets.remove(self.handle); + self.stack.with_mut(|i| i.sockets.remove(self.handle)); } } + +fn _assert_covariant<'a, 'b: 'a>(x: RawSocket<'b>) -> RawSocket<'a> { + x +} diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index be0e1a129..d0230b581 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -8,12 +8,10 @@ //! Incoming connections when no socket is listening are rejected. To accept many incoming //! connections, create many sockets and put them all into listening mode. -use core::cell::RefCell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::mem; -use core::task::Poll; +use core::task::{Context, Poll}; -use embassy_net_driver::Driver; use embassy_time::Duration; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::tcp; @@ -21,7 +19,7 @@ pub use smoltcp::socket::tcp::State; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; use crate::time::duration_to_smoltcp; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by TcpSocket read/write functions. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -75,10 +73,29 @@ pub struct TcpWriter<'a> { } impl<'a> TcpReader<'a> { + /// Wait until the socket becomes readable. + /// + /// A socket becomes readable when the receive half of the full-duplex connection is open + /// (see [`may_recv()`](TcpSocket::may_recv)), and there is some pending data in the receive buffer. + /// + /// This is the equivalent of [read](#method.read), without buffering any data. + pub fn wait_read_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_read_ready(cx)) + } + /// Read data from the socket. /// /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. + /// + /// # Note + /// A return value of Ok(0) means that we have read all data and the remote + /// side has closed our receive half of the socket. The remote can no longer + /// send bytes. + /// + /// The send half of the socket is still open. If you want to reconnect using + /// the socket you split this reader off the send half needs to be closed using + /// [`abort()`](TcpSocket::abort). pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } @@ -108,20 +125,30 @@ impl<'a> TcpReader<'a> { } impl<'a> TcpWriter<'a> { + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when the transmit half of the full-duplex connection is open + /// (see [`may_send()`](TcpSocket::may_send)), and the transmit buffer is not full. + /// + /// This is the equivalent of [write](#method.write), without sending any data. + pub fn wait_write_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_write_ready(cx)) + } + /// Write data to the socket. /// /// Returns how many bytes were written, or an error. If the socket is not ready to /// accept data, it waits until it is. - pub async fn write(&mut self, buf: &[u8]) -> Result { - self.io.write(buf).await + pub fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { + self.io.write(buf) } /// Flushes the written data to the socket. /// /// This waits until all data has been sent, and ACKed by the remote host. For a connection /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. - pub async fn flush(&mut self) -> Result<(), Error> { - self.io.flush().await + pub fn flush(&mut self) -> impl Future> + '_ { + self.io.flush() } /// Call `f` with the largest contiguous slice of octets in the transmit buffer, @@ -148,20 +175,18 @@ impl<'a> TcpWriter<'a> { impl<'a> TcpSocket<'a> { /// Create a new TCP socket on the given stack, with the given buffers. - pub fn new(stack: &'a Stack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { - let s = &mut *stack.socket.borrow_mut(); - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(tcp::Socket::new( - tcp::SocketBuffer::new(rx_buffer), - tcp::SocketBuffer::new(tx_buffer), - )); + pub fn new(stack: Stack<'a>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { + let handle = stack.with_mut(|i| { + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(tcp::Socket::new( + tcp::SocketBuffer::new(rx_buffer), + tcp::SocketBuffer::new(tx_buffer), + )) + }); Self { - io: TcpIo { - stack: &stack.socket, - handle, - }, + io: TcpIo { stack, handle }, } } @@ -219,7 +244,7 @@ impl<'a> TcpSocket<'a> { where T: Into, { - let local_port = self.io.stack.borrow_mut().get_local_port(); + let local_port = self.io.stack.with_mut(|i| i.get_local_port()); match { self.io @@ -269,34 +294,61 @@ impl<'a> TcpSocket<'a> { .await } + /// Wait until the socket becomes readable. + /// + /// A socket becomes readable when the receive half of the full-duplex connection is open + /// (see [may_recv](#method.may_recv)), and there is some pending data in the receive buffer. + /// + /// This is the equivalent of [read](#method.read), without buffering any data. + pub fn wait_read_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_read_ready(cx)) + } + /// Read data from the socket. /// /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. - pub async fn read(&mut self, buf: &mut [u8]) -> Result { - self.io.read(buf).await + /// + /// A return value of Ok(0) means that the socket was closed and is longer + /// able to receive any data. + pub fn read<'s>(&'s mut self, buf: &'s mut [u8]) -> impl Future> + 's { + self.io.read(buf) + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + /// + /// This is the equivalent of [write](#method.write), without sending any data. + pub fn wait_write_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_write_ready(cx)) } /// Write data to the socket. /// /// Returns how many bytes were written, or an error. If the socket is not ready to /// accept data, it waits until it is. - pub async fn write(&mut self, buf: &[u8]) -> Result { - self.io.write(buf).await + pub fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { + self.io.write(buf) } /// Flushes the written data to the socket. /// /// This waits until all data has been sent, and ACKed by the remote host. For a connection /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. - pub async fn flush(&mut self) -> Result<(), Error> { - self.io.flush().await + pub fn flush(&mut self) -> impl Future> + '_ { + self.io.flush() } /// Set the timeout for the socket. /// /// If the timeout is set, the socket will be closed if no data is received for the /// specified duration. + /// + /// # Note: + /// Set a keep alive interval ([`set_keep_alive`] to prevent timeouts when + /// the remote could still respond. pub fn set_timeout(&mut self, duration: Option) { self.io .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) @@ -308,6 +360,9 @@ impl<'a> TcpSocket<'a> { /// the specified duration of inactivity. /// /// If not set, the socket will not send keep-alive packets. + /// + /// By setting a [`timeout`](Self::timeout) larger then the keep alive you + /// can detect a remote endpoint that no longer answers. pub fn set_keep_alive(&mut self, interval: Option) { self.io .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) @@ -361,11 +416,25 @@ impl<'a> TcpSocket<'a> { self.io.with_mut(|s, _| s.abort()) } - /// Get whether the socket is ready to send data, i.e. whether there is space in the send buffer. + /// Return whether the transmit half of the full-duplex connection is open. + /// + /// This function returns true if it's possible to send data and have it arrive + /// to the remote endpoint. However, it does not make any guarantees about the state + /// of the transmit buffer, and even if it returns true, [write](#method.write) may + /// not be able to enqueue any octets. + /// + /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or + /// `CLOSE-WAIT` state. pub fn may_send(&self) -> bool { self.io.with(|s, _| s.may_send()) } + /// Check whether the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + pub fn can_send(&self) -> bool { + self.io.with(|s, _| s.can_send()) + } + /// 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, @@ -382,35 +451,58 @@ impl<'a> TcpSocket<'a> { impl<'a> Drop for TcpSocket<'a> { fn drop(&mut self) { - self.io.stack.borrow_mut().sockets.remove(self.io.handle); + self.io.stack.with_mut(|i| i.sockets.remove(self.io.handle)); } } +fn _assert_covariant<'a, 'b: 'a>(x: TcpSocket<'b>) -> TcpSocket<'a> { + x +} +fn _assert_covariant_reader<'a, 'b: 'a>(x: TcpReader<'b>) -> TcpReader<'a> { + x +} +fn _assert_covariant_writer<'a, 'b: 'a>(x: TcpWriter<'b>) -> TcpWriter<'a> { + x +} + // ======================= #[derive(Copy, Clone)] struct TcpIo<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'d> TcpIo<'d> { fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.borrow(); - let socket = s.sockets.get::(self.handle); - f(socket, &s.iface) + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) } - fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + fn with_mut(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) } - async fn read(&mut self, buf: &mut [u8]) -> Result { - poll_fn(move |cx| { + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + fn read<'s>(&'s mut self, buf: &'s mut [u8]) -> impl Future> + 's { + poll_fn(|cx| { // CAUTION: smoltcp semantics around EOF are different to what you'd expect // from posix-like IO, so we have to tweak things here. self.with_mut(|s, _| match s.recv_slice(buf) { @@ -434,11 +526,21 @@ impl<'d> TcpIo<'d> { Err(tcp::RecvError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), }) }) - .await } - async fn write(&mut self, buf: &[u8]) -> Result { - poll_fn(move |cx| { + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { + poll_fn(|cx| { self.with_mut(|s, _| match s.send_slice(buf) { // Not ready to send (no space in the tx buffer) Ok(0) => { @@ -451,7 +553,6 @@ impl<'d> TcpIo<'d> { Err(tcp::SendError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), }) }) - .await } async fn write_with(&mut self, f: F) -> Result @@ -512,10 +613,10 @@ impl<'d> TcpIo<'d> { .await } - async fn flush(&mut self) -> Result<(), Error> { - poll_fn(move |cx| { + fn flush(&mut self) -> impl Future> + '_ { + poll_fn(|cx| { self.with_mut(|s, _| { - let data_pending = s.send_queue() > 0; + let data_pending = (s.send_queue() > 0) && s.state() != tcp::State::Closed; let fin_pending = matches!( s.state(), tcp::State::FinWait1 | tcp::State::Closing | tcp::State::LastAck @@ -533,7 +634,6 @@ impl<'d> TcpIo<'d> { } }) }) - .await } fn recv_capacity(&self) -> usize { @@ -648,24 +748,23 @@ mod embedded_io_impls { pub mod client { use core::cell::{Cell, UnsafeCell}; use core::mem::MaybeUninit; + use core::net::IpAddr; use core::ptr::NonNull; - use embedded_nal_async::IpAddr; - use super::*; /// TCP client connection pool compatible with `embedded-nal-async` traits. /// /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. - pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { - stack: &'d Stack, + pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + stack: Stack<'d>, state: &'d TcpClientState, socket_timeout: Option, } - impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> { /// Create a new `TcpClient`. - pub fn new(stack: &'d Stack, state: &'d TcpClientState) -> Self { + pub fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Self { Self { stack, state, @@ -682,29 +781,29 @@ pub mod client { } } - impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect - for TcpClient<'d, D, N, TX_SZ, RX_SZ> + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + for TcpClient<'d, N, TX_SZ, RX_SZ> { type Error = Error; - type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; + type Connection<'m> + = TcpConnection<'m, N, TX_SZ, RX_SZ> + where + Self: 'm; - async fn connect<'a>( - &'a self, - remote: embedded_nal_async::SocketAddr, - ) -> Result, Self::Error> { + async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result, Self::Error> { let addr: crate::IpAddress = match remote.ip() { #[cfg(feature = "proto-ipv4")] - IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())), + IpAddr::V4(addr) => crate::IpAddress::Ipv4(addr), #[cfg(not(feature = "proto-ipv4"))] IpAddr::V4(_) => panic!("ipv4 support not enabled"), #[cfg(feature = "proto-ipv6")] - IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())), + IpAddr::V6(addr) => crate::IpAddress::Ipv6(addr), #[cfg(not(feature = "proto-ipv6"))] IpAddr::V6(_) => panic!("ipv6 support not enabled"), }; let remote_endpoint = (addr, remote.port()); - let mut socket = TcpConnection::new(&self.stack, self.state)?; - socket.socket.set_timeout(self.socket_timeout.clone()); + let mut socket = TcpConnection::new(self.stack, self.state)?; + socket.socket.set_timeout(self.socket_timeout); socket .socket .connect(remote_endpoint) @@ -722,7 +821,7 @@ pub mod client { } impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> { - fn new(stack: &'d Stack, state: &'d TcpClientState) -> Result { + fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Result { let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; Ok(Self { socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 6e50c4e01..63c2f4c75 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -1,17 +1,15 @@ //! UDP sockets. -use core::cell::RefCell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::mem; use core::task::{Context, Poll}; -use embassy_net_driver::Driver; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::udp; pub use smoltcp::socket::udp::{PacketMetadata, UdpMetadata}; use smoltcp::wire::IpListenEndpoint; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by [`UdpSocket::bind`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -23,7 +21,7 @@ pub enum BindError { NoRoute, } -/// Error returned by [`UdpSocket::recv_from`] and [`UdpSocket::send_to`]. +/// Error returned by [`UdpSocket::send_to`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SendError { @@ -31,9 +29,11 @@ pub enum SendError { NoRoute, /// Socket not bound to an outgoing port. SocketNotBound, + /// There is not enough transmit buffer capacity to ever send this packet. + PacketTooLarge, } -/// Error returned by [`UdpSocket::recv_from`] and [`UdpSocket::send_to`]. +/// Error returned by [`UdpSocket::recv_from`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RecvError { @@ -43,34 +43,31 @@ pub enum RecvError { /// An UDP socket. pub struct UdpSocket<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'a> UdpSocket<'a> { /// Create a new UDP socket using the provided stack and buffers. - pub fn new( - stack: &'a Stack, + pub fn new( + stack: Stack<'a>, rx_meta: &'a mut [PacketMetadata], rx_buffer: &'a mut [u8], tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - let s = &mut *stack.socket.borrow_mut(); + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(udp::Socket::new( + udp::PacketBuffer::new(rx_meta, rx_buffer), + udp::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); - let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(udp::Socket::new( - udp::PacketBuffer::new(rx_meta, rx_buffer), - udp::PacketBuffer::new(tx_meta, tx_buffer), - )); - - Self { - stack: &stack.socket, - handle, - } + Self { stack, handle } } /// Bind the socket to a local endpoint. @@ -82,7 +79,7 @@ impl<'a> UdpSocket<'a> { if endpoint.port == 0 { // If user didn't specify port allocate a dynamic port. - endpoint.port = self.stack.borrow_mut().get_local_port(); + endpoint.port = self.stack.with_mut(|i| i.get_local_port()); } match self.with_mut(|s, _| s.bind(endpoint)) { @@ -93,17 +90,45 @@ impl<'a> UdpSocket<'a> { } fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.borrow(); - let socket = s.sockets.get::(self.handle); - f(socket, &s.iface) + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) } fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub fn wait_recv_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_recv_ready(cx)) + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) } /// Receive a datagram. @@ -111,8 +136,11 @@ impl<'a> UdpSocket<'a> { /// This method will wait until a datagram is received. /// /// Returns the number of bytes received and the remote endpoint. - pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, UdpMetadata), RecvError> { - poll_fn(move |cx| self.poll_recv_from(buf, cx)).await + pub fn recv_from<'s>( + &'s self, + buf: &'s mut [u8], + ) -> impl Future> + 's { + poll_fn(|cx| self.poll_recv_from(buf, cx)) } /// Receive a datagram. @@ -138,10 +166,68 @@ impl<'a> UdpSocket<'a> { }) } + /// Receive a datagram with a zero-copy function. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will call the provided function + /// with the number of bytes received and the remote endpoint and return + /// `Poll::Ready` with the function's returned value. + pub async fn recv_from_with(&mut self, f: F) -> R + where + F: FnOnce(&[u8], UdpMetadata) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.recv() { + Ok((buffer, endpoint)) => Poll::Ready(unwrap!(f.take())(buffer, endpoint)), + Err(udp::RecvError::Truncated) => unreachable!(), + Err(udp::RecvError::Exhausted) => { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + } + }) + }) + .await + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub fn wait_send_ready(&self) -> impl Future + '_ { + poll_fn(|cx| self.poll_send_ready(cx)) + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + /// Send a datagram to the specified remote endpoint. /// /// This method will wait until the datagram has been sent. /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Err(SendError::PacketTooLarge)` + /// /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` pub async fn send_to(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError> where @@ -158,11 +244,19 @@ impl<'a> UdpSocket<'a> { /// When the socket's send buffer is full, this method will return `Poll::Pending` /// and register the current task to be notified when the buffer has space available. /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Poll::Ready(Err(SendError::PacketTooLarge))` + /// /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`. pub fn poll_send_to(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll> where T: Into, { + // Don't need to wake waker in `with_mut` if the buffer will never fit the udp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < buf.len()); + if send_capacity_too_small { + return Poll::Ready(Err(SendError::PacketTooLarge)); + } + self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { // Entire datagram has been sent Ok(()) => Poll::Ready(Ok(())), @@ -181,6 +275,64 @@ impl<'a> UdpSocket<'a> { }) } + /// Send a datagram to the specified remote endpoint with a zero-copy function. + /// + /// This method will wait until the buffer can fit the requested size before + /// calling the function to fill its contents. + /// + /// If the socket's send buffer is too small to fit `size`, this method will return `Err(SendError::PacketTooLarge)` + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to_with(&mut self, size: usize, remote_endpoint: T, f: F) -> Result + where + T: Into + Copy, + F: FnOnce(&mut [u8]) -> R, + { + // Don't need to wake waker in `with_mut` if the buffer will never fit the udp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < size); + if send_capacity_too_small { + return Err(SendError::PacketTooLarge); + } + + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.send(size, remote_endpoint) { + Ok(buffer) => Poll::Ready(Ok(unwrap!(f.take())(buffer))), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.endpoint().port == 0 { + Poll::Ready(Err(SendError::SocketNotBound)) + } else { + Poll::Ready(Err(SendError::NoRoute)) + } + } + } + }) + }) + .await + } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub fn flush(&mut self) -> impl Future + '_ { + poll_fn(|cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + } + /// Returns the local endpoint of the socket. pub fn endpoint(&self) -> IpListenEndpoint { self.with(|s, _| s.endpoint()) @@ -235,6 +387,10 @@ impl<'a> UdpSocket<'a> { impl Drop for UdpSocket<'_> { fn drop(&mut self) { - self.stack.borrow_mut().sockets.remove(self.handle); + self.stack.with_mut(|i| i.sockets.remove(self.handle)); } } + +fn _assert_covariant<'a, 'b: 'a>(x: UdpSocket<'b>) -> UdpSocket<'a> { + x +} diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index f8d6ab753..ffa7997f7 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.3.1 - 2025-01-09 + +- bugfix: nrf twim return errors in async\_wait instead of waiting indefinitely +- bugfix: fix missing setting input as disconnected. +- changed: Modify Uarte and BufferedUarte initialization to take pins before interrupts ([#3983](https://github.com/embassy-rs/embassy/pull/3983)) + + +## 0.3.0 - 2025-01-06 + +Firstly, this release switches embassy-nrf to chiptool-based `nrf-pac` +implementations and lots of improvements, but also changes to API like +peripheral and interrupt naming. + +Second big change is a refactoring of time driver contract with +embassy-time-driver. From now on, the timer queue is handled by the +time-driver implementation and `generic-queue` feature is provided by +the `embassy-time-queue-utils` crate. Newly required dependencies are +following: + - embassy-time-0.4 + - embassy-time-driver-0.2 + - embassy-time-queue-utils-0.1 + +Add support for following NRF chips: + - nRF54L15 (only gpio and timer support) + +Support for chip-specific features: + - RESET operations for nrf5340 + - POWER operations (system-off and wake-on-field) for nrf52840 and nrf9160 + +- nfc: + - Adds support for NFC Tag emulator driver +- pwm: + - Fix incorrect pin assignments + - Properly disconnect inputs when pins are set as output +- uart: + - `try_write` support for `BufferedUarte` + - Support for `embedded_io_async` trait +- spim: + - Support SPIM4 peripheral on nrf5340-app +- time: + - Generic refactor of embassy-time-driver API + - Fix for missed executor alarms in certain occasions (issue #3672, PR #3705). +- twim: + - Implement support for transactions + - Remove support for consecutive Read operations due to hardware limitations + ## 0.2.0 - 2024-08-05 - Support for NRF chips: diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 3e66d6886..47eb92697 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-nrf" -version = "0.2.0" +version = "0.3.1" edition = "2021" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for nRF series microcontrollers" @@ -18,6 +18,7 @@ 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 = "nrf54.*", target = "thumbv8m.main-none-eabihf" }, { regex_feature = "nrf91.*", target = "thumbv8m.main-none-eabihf" }, ] @@ -28,23 +29,10 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["rt"] ## Cortex-M runtime (enabled by default) -rt = [ - "nrf51-pac?/rt", - "nrf52805-pac?/rt", - "nrf52810-pac?/rt", - "nrf52811-pac?/rt", - "nrf52820-pac?/rt", - "nrf52832-pac?/rt", - "nrf52833-pac?/rt", - "nrf52840-pac?/rt", - "nrf5340-app-pac?/rt", - "nrf5340-net-pac?/rt", - "nrf9160-pac?/rt", - "nrf9120-pac?/rt", -] +rt = ["nrf-pac/rt"] ## Enable features requiring `embassy-time` -time = ["dep:embassy-time"] +time = ["dep:embassy-time", "embassy-embedded-hal/time"] ## Enable defmt defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "embassy-usb-driver/defmt", "embassy-embedded-hal/defmt"] @@ -69,33 +57,41 @@ nfc-pins-as-gpio = [] ## * nRF52820, nRF52833, nRF52840: P0_18 reset-pin-as-gpio = [] +## Allow using the LFXO pins as regular GPIO pins (P0_00/P0_01 on nRF53) +lfxo-pins-as-gpio = [] + ## Implements the MultiwriteNorFlash trait for QSPI. Should only be enabled if your external ## flash supports the semantics described [here](https://docs.rs/embedded-storage/0.3.1/embedded_storage/nor_flash/trait.MultiwriteNorFlash.html) qspi-multiwrite-flash = [] #! ### Chip selection features ## nRF51 -nrf51 = ["nrf51-pac", "_nrf51"] +nrf51 = ["nrf-pac/nrf51", "_nrf51"] ## nRF52805 -nrf52805 = ["nrf52805-pac", "_nrf52"] +nrf52805 = ["nrf-pac/nrf52805", "_nrf52"] ## nRF52810 -nrf52810 = ["nrf52810-pac", "_nrf52"] +nrf52810 = ["nrf-pac/nrf52810", "_nrf52"] ## nRF52811 -nrf52811 = ["nrf52811-pac", "_nrf52"] +nrf52811 = ["nrf-pac/nrf52811", "_nrf52"] ## nRF52820 -nrf52820 = ["nrf52820-pac", "_nrf52"] +nrf52820 = ["nrf-pac/nrf52820", "_nrf52"] ## nRF52832 -nrf52832 = ["nrf52832-pac", "_nrf52", "_nrf52832_anomaly_109"] +nrf52832 = ["nrf-pac/nrf52832", "_nrf52", "_nrf52832_anomaly_109"] ## nRF52833 -nrf52833 = ["nrf52833-pac", "_nrf52", "_gpio-p1"] +nrf52833 = ["nrf-pac/nrf52833", "_nrf52", "_gpio-p1"] ## nRF52840 -nrf52840 = ["nrf52840-pac", "_nrf52", "_gpio-p1"] +nrf52840 = ["nrf-pac/nrf52840", "_nrf52", "_gpio-p1"] ## nRF5340 application core in Secure mode nrf5340-app-s = ["_nrf5340-app", "_s"] ## nRF5340 application core in Non-Secure mode nrf5340-app-ns = ["_nrf5340-app", "_ns"] ## nRF5340 network core nrf5340-net = ["_nrf5340-net"] +## nRF54L15 application core in Secure mode +nrf54l15-app-s = ["_nrf54l15-app", "_s"] +## nRF54L15 application core in Non-Secure mode +nrf54l15-app-ns = ["_nrf54l15-app", "_ns"] + ## nRF9160 in Secure mode nrf9160-s = ["_nrf9160", "_s", "_nrf91"] ## nRF9160 in Non-Secure mode @@ -113,16 +109,20 @@ nrf9161-ns = ["nrf9120-ns"] # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. -_nrf5340-app = ["_nrf5340", "nrf5340-app-pac"] -_nrf5340-net = ["_nrf5340", "nrf5340-net-pac"] +_nrf5340-app = ["_nrf5340", "_multi_wdt", "nrf-pac/nrf5340-app"] +_nrf5340-net = ["_nrf5340", "nrf-pac/nrf5340-net"] _nrf5340 = ["_gpio-p1", "_dppi"] -_nrf9160 = ["nrf9160-pac", "_dppi"] -_nrf9120 = ["nrf9120-pac", "_dppi"] +_nrf54l15-app = ["_nrf54l15", "nrf-pac/nrf54l15-app"] +_nrf54l15 = ["_nrf54l", "_gpio-p1", "_gpio-p2"] +_nrf54l = ["_dppi"] + +_nrf9160 = ["nrf-pac/nrf9160", "_dppi"] +_nrf9120 = ["nrf-pac/nrf9120", "_dppi"] _nrf52 = ["_ppi"] _nrf51 = ["_ppi"] _nrf91 = [] -_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768"] +_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"] # trustzone state. _s = [] @@ -131,17 +131,22 @@ _ns = [] _ppi = [] _dppi = [] _gpio-p1 = [] +_gpio-p2 = [] # Errata workarounds _nrf52832_anomaly_109 = [] +# watchdog timer +_multi_wdt = [] + [dependencies] -embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } -embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } -embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } -embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal", default-features = false } +embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } @@ -149,28 +154,19 @@ embedded-hal-async = { version = "1.0" } embedded-io = { version = "0.6.0" } embedded-io-async = { version = "0.6.1" } -defmt = { version = "0.3", optional = true } +rand-core-06 = { package = "rand_core", version = "0.6" } +rand-core-09 = { package = "rand_core", version = "0.9" } + +nrf-pac = "0.1.0" + +defmt = { version = "1.0.1", 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" critical-section = "1.1" -rand_core = "0.6.3" fixed = "1.10.0" embedded-storage = "0.3.1" 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 } -nrf52820-pac = { version = "0.12.0", optional = true } -nrf52832-pac = { version = "0.12.0", optional = true } -nrf52833-pac = { version = "0.12.0", optional = true } -nrf52840-pac = { version = "0.12.0", optional = true } -nrf5340-app-pac = { version = "0.12.0", optional = true } -nrf5340-net-pac = { version = "0.12.0", optional = true } -nrf9160-pac = { version = "0.12.0", optional = true } -nrf9120-pac = { version = "0.12.0", optional = true } diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 159b4db8f..29e126903 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -9,25 +9,26 @@ //! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. use core::cmp::min; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::slice; use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU8, AtomicUsize, Ordering}; use core::task::Poll; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::Peri; +use pac::uarte::vals; // 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}; +pub use pac::uarte::vals::{Baudrate, ConfigParity as Parity}; -use crate::gpio::{AnyPin, Pin as GpioPin, PselBits, SealedPin}; +use crate::gpio::{AnyPin, Pin as GpioPin}; 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::{configure, drop_tx_rx, Config, Instance as UarteInstance}; -use crate::{interrupt, pac, Peripheral, EASY_DMA_SIZE}; +use crate::uarte::{configure, configure_rx_pins, configure_tx_pins, drop_tx_rx, Config, Instance as UarteInstance}; +use crate::{interrupt, pac, EASY_DMA_SIZE}; pub(crate) struct State { tx_buf: RingBuffer, @@ -79,57 +80,57 @@ impl interrupt::typelevel::Handler for Interrupt 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() != 0 { + r.events_error().write_value(0); + let errs = r.errorsrc().read(); + r.errorsrc().write_value(errs); - if errs.overrun().bit() { + if errs.overrun() { 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(); + if r.inten().read().rxdrdy() && r.events_rxdrdy().read() != 0 { + r.intenclr().write(|w| w.set_rxdrdy(true)); + r.events_rxdrdy().write_value(0); ss.rx_waker.wake(); } - if r.events_endrx.read().bits() != 0 { + if r.events_endrx().read() != 0 { //trace!(" irq_rx: endrx"); - r.events_endrx.reset(); + r.events_endrx().write_value(0); 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) { + if r.events_rxstarted().read() != 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(); + r.events_rxstarted().write_value(0); //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 _) }); + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(half_len as _)); 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) }); + ppi::regs().chenset().write(|w| w.0 = 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. // check again in case endrx has happened between the last check and now. - if r.events_endrx.read().bits() != 0 { + if r.events_endrx().read() != 0 { //trace!(" irq_rx: endrx"); - r.events_endrx.reset(); + r.events_endrx().write_value(0); let val = s.rx_ended_count.load(Ordering::Relaxed); s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); @@ -144,7 +145,7 @@ impl interrupt::typelevel::Handler for Interrupt // 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; + let ppi_ch_enabled = ppi::regs().chen().read().ch(chn as _); // 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. @@ -152,10 +153,10 @@ impl interrupt::typelevel::Handler for Interrupt //trace!("manually starting."); // disable the ppi ch, it's of no use anymore. - ppi::regs().chenclr.write(|w| unsafe { w.bits(1 << chn) }); + ppi::regs().chenclr().write(|w| w.set_ch(chn as _, true)); // manually start - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + r.tasks_startrx().write_value(1); } rx.push_done(half_len); @@ -164,7 +165,7 @@ impl interrupt::typelevel::Handler for Interrupt s.rx_started.store(true, Ordering::Relaxed); } else { //trace!(" irq_rx: rxstarted no buf"); - r.intenclr.write(|w| w.rxstarted().clear()); + r.intenclr().write(|w| w.set_rxstarted(true)); } } } @@ -173,8 +174,8 @@ impl interrupt::typelevel::Handler for Interrupt if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { // TX end - if r.events_endtx.read().bits() != 0 { - r.events_endtx.reset(); + if r.events_endtx().read() != 0 { + r.events_endtx().write_value(0); let n = s.tx_count.load(Ordering::Relaxed); //trace!(" irq_tx: endtx {:?}", n); @@ -192,11 +193,11 @@ impl interrupt::typelevel::Handler for Interrupt 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 _) }); + r.txd().ptr().write_value(ptr as u32); + r.txd().maxcnt().write(|w| w.set_maxcnt(len as _)); // Start UARTE Transmit transaction - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + r.tasks_starttx().write_value(1); } } } @@ -221,27 +222,26 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { /// Panics if `rx_buffer.len()` is odd. #[allow(clippy::too_many_arguments)] pub fn new( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - ppi_group: impl Peripheral

+ 'd, + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

+ 'd, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(uarte, timer, rxd, txd, ppi_ch1, ppi_ch2, ppi_group); Self::new_inner( uarte, timer, - ppi_ch1.map_into(), - ppi_ch2.map_into(), - ppi_group.map_into(), - rxd.map_into(), - txd.map_into(), + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), + txd.into(), None, None, config, @@ -257,31 +257,30 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { /// Panics if `rx_buffer.len()` is odd. #[allow(clippy::too_many_arguments)] pub fn new_with_rtscts( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - ppi_group: impl Peripheral

+ 'd, + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

+ 'd, - cts: impl Peripheral

+ 'd, - rts: impl Peripheral

+ 'd, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(uarte, timer, rxd, txd, cts, rts, ppi_ch1, ppi_ch2, ppi_group); Self::new_inner( uarte, timer, - ppi_ch1.map_into(), - ppi_ch2.map_into(), - ppi_group.map_into(), - rxd.map_into(), - txd.map_into(), - Some(cts.map_into()), - Some(rts.map_into()), + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), + txd.into(), + Some(cts.into()), + Some(rts.into()), config, rx_buffer, tx_buffer, @@ -290,15 +289,15 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { #[allow(clippy::too_many_arguments)] 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>, - txd: PeripheralRef<'d, AnyPin>, - cts: Option>, - rts: Option>, + peri: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, AnyConfigurableChannel>, + ppi_ch2: Peri<'d, AnyConfigurableChannel>, + ppi_group: Peri<'d, AnyGroup>, + rxd: Peri<'d, AnyPin>, + txd: Peri<'d, AnyPin>, + cts: Option>, + rts: Option>, config: Config, rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], @@ -308,7 +307,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { 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::regs().enable.write(|w| w.enable().enabled()); + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); U::Interrupt::pend(); unsafe { U::Interrupt::enable() }; @@ -320,7 +319,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { /// 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)); + r.baudrate().write(|w| w.set_baudrate(baudrate)); } /// Split the UART in reader and writer parts. @@ -358,6 +357,11 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { self.tx.write(buf).await } + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + self.tx.try_write(buf) + } + /// 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 @@ -366,20 +370,19 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { /// Reader part of the buffered UARTE driver. pub struct BufferedUarteTx<'d, U: UarteInstance> { - _peri: PeripheralRef<'d, U>, + _peri: Peri<'d, U>, } impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { /// Create a new BufferedUarteTx without hardware flow control. pub fn new( - uarte: impl Peripheral

+ 'd, + uarte: Peri<'d, U>, + txd: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - txd: impl Peripheral

+ 'd, config: Config, tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(uarte, txd); - Self::new_inner(uarte, txd.map_into(), None, config, tx_buffer) + Self::new_inner(uarte, txd.into(), None, config, tx_buffer) } /// Create a new BufferedUarte with hardware flow control (RTS/CTS) @@ -388,21 +391,20 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { /// /// Panics if `rx_buffer.len()` is odd. pub fn new_with_cts( - uarte: impl Peripheral

+ 'd, + uarte: Peri<'d, U>, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - txd: impl Peripheral

+ 'd, - cts: impl Peripheral

+ '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) + Self::new_inner(uarte, txd.into(), Some(cts.into()), config, tx_buffer) } fn new_inner( - peri: PeripheralRef<'d, U>, - txd: PeripheralRef<'d, AnyPin>, - cts: Option>, + peri: Peri<'d, U>, + txd: Peri<'d, AnyPin>, + cts: Option>, config: Config, tx_buffer: &'d mut [u8], ) -> Self { @@ -410,7 +412,7 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { let this = Self::new_innerer(peri, txd, cts, tx_buffer); - U::regs().enable.write(|w| w.enable().enabled()); + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); U::Interrupt::pend(); unsafe { U::Interrupt::enable() }; @@ -420,21 +422,14 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { } fn new_innerer( - peri: PeripheralRef<'d, U>, - txd: PeripheralRef<'d, AnyPin>, - cts: Option>, + peri: Peri<'d, U>, + txd: Peri<'d, AnyPin>, + cts: Option>, tx_buffer: &'d mut [u8], ) -> Self { let r = U::regs(); - txd.set_high(); - txd.conf().write(|w| w.dir().output().drive().h0h1()); - r.psel.txd.write(|w| unsafe { w.bits(txd.psel_bits()) }); - - if let Some(pin) = &cts { - pin.conf().write(|w| w.input().connect().drive().h0h1()); - } - r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) }); + configure_tx_pins(r, txd, cts); // Initialize state let s = U::buffered_state(); @@ -442,19 +437,18 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { let len = tx_buffer.len(); unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; - r.events_txstarted.reset(); + r.events_txstarted().write_value(0); // Enable interrupts - r.intenset.write(|w| { - w.endtx().set(); - w + r.intenset().write(|w| { + w.set_endtx(true); }); Self { _peri: peri } } /// Write a buffer into this writer, returning how many bytes were written. - pub async fn write(&mut self, buf: &[u8]) -> Result { + pub fn write<'a>(&'a mut self, buf: &'a [u8]) -> impl Future> + 'a { poll_fn(move |cx| { //trace!("poll_write: {:?}", buf.len()); let ss = U::state(); @@ -479,11 +473,33 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { Poll::Ready(Ok(n)) }) - .await + } + + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + //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() { + return Ok(0); + } + + 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(); + + Ok(n) } /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. - pub async fn flush(&mut self) -> Result<(), Error> { + pub fn flush(&mut self) -> impl Future> + '_ { poll_fn(move |cx| { //trace!("poll_flush"); let ss = U::state(); @@ -496,7 +512,6 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { Poll::Ready(Ok(())) }) - .await } } @@ -504,15 +519,14 @@ 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.intenclr().write(|w| { + w.set_txdrdy(true); + w.set_txstarted(true); + w.set_txstopped(true); }); - r.events_txstopped.reset(); - r.tasks_stoptx.write(|w| unsafe { w.bits(1) }); - while r.events_txstopped.read().bits() == 0 {} + r.events_txstopped().write_value(0); + r.tasks_stoptx().write_value(1); + while r.events_txstopped().read() == 0 {} let s = U::buffered_state(); unsafe { s.tx_buf.deinit() } @@ -524,7 +538,7 @@ impl<'a, U: UarteInstance> Drop for BufferedUarteTx<'a, U> { /// Reader part of the buffered UARTE driver. pub struct BufferedUarteRx<'d, U: UarteInstance, T: TimerInstance> { - _peri: PeripheralRef<'d, U>, + _peri: Peri<'d, U>, timer: Timer<'d, T>, _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 1>, _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 2>, @@ -539,24 +553,23 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { /// Panics if `rx_buffer.len()` is odd. #[allow(clippy::too_many_arguments)] pub fn new( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - ppi_group: impl Peripheral

+ 'd, + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

+ 'd, + rxd: Peri<'d, impl GpioPin>, 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(), + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), None, config, rx_buffer, @@ -570,26 +583,25 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { /// Panics if `rx_buffer.len()` is odd. #[allow(clippy::too_many_arguments)] pub fn new_with_rts( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - ppi_group: impl Peripheral

+ 'd, + uarte: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, impl ConfigurableChannel>, + ppi_ch2: Peri<'d, impl ConfigurableChannel>, + ppi_group: Peri<'d, impl Group>, + rxd: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

+ 'd, - rts: impl Peripheral

+ '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()), + ppi_ch1.into(), + ppi_ch2.into(), + ppi_group.into(), + rxd.into(), + Some(rts.into()), config, rx_buffer, ) @@ -597,13 +609,13 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { #[allow(clippy::too_many_arguments)] 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>, + peri: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, AnyConfigurableChannel>, + ppi_ch2: Peri<'d, AnyConfigurableChannel>, + ppi_group: Peri<'d, AnyGroup>, + rxd: Peri<'d, AnyPin>, + rts: Option>, config: Config, rx_buffer: &'d mut [u8], ) -> Self { @@ -611,7 +623,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { let this = Self::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); - U::regs().enable.write(|w| w.enable().enabled()); + U::regs().enable().write(|w| w.set_enable(vals::Enable::ENABLED)); U::Interrupt::pend(); unsafe { U::Interrupt::enable() }; @@ -622,27 +634,20 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { #[allow(clippy::too_many_arguments)] 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>, + peri: Peri<'d, U>, + timer: Peri<'d, T>, + ppi_ch1: Peri<'d, AnyConfigurableChannel>, + ppi_ch2: Peri<'d, AnyConfigurableChannel>, + ppi_group: Peri<'d, AnyGroup>, + rxd: Peri<'d, AnyPin>, + rts: Option>, 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()); - } - r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); + configure_rx_pins(r, rxd, rts); // Initialize state let s = U::buffered_state(); @@ -653,20 +658,19 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { unsafe { s.rx_buf.init(rx_buffer.as_mut_ptr(), rx_len) }; // clear errors - let errors = r.errorsrc.read().bits(); - r.errorsrc.write(|w| unsafe { w.bits(errors) }); + let errors = r.errorsrc().read(); + r.errorsrc().write_value(errors); - r.events_rxstarted.reset(); - r.events_error.reset(); - r.events_endrx.reset(); + r.events_rxstarted().write_value(0); + r.events_error().write_value(0); + r.events_endrx().write_value(0); // Enable interrupts - r.intenset.write(|w| { - w.endtx().set(); - w.rxstarted().set(); - w.error().set(); - w.endrx().set(); - w + r.intenset().write(|w| { + w.set_endtx(true); + w.set_rxstarted(true); + w.set_error(true); + w.set_endrx(true); }); // Configure byte counter. @@ -676,15 +680,15 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { timer.clear(); timer.start(); - let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(&r.events_rxdrdy), timer.task_count()); + let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(r.events_rxdrdy()), timer.task_count()); ppi_ch1.enable(); s.rx_ppi_ch.store(ppi_ch2.number() as u8, Ordering::Relaxed); let mut ppi_group = PpiGroup::new(ppi_group); let mut ppi_ch2 = Ppi::new_one_to_two( ppi_ch2, - Event::from_reg(&r.events_endrx), - Task::from_reg(&r.tasks_startrx), + Event::from_reg(r.events_endrx()), + Task::from_reg(r.tasks_startrx()), ppi_group.task_disable_all(), ); ppi_ch2.disable(); @@ -709,7 +713,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { } /// 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> { + pub fn fill_buf(&mut self) -> impl Future> { poll_fn(move |cx| { compiler_fence(Ordering::SeqCst); //trace!("poll_read"); @@ -719,8 +723,8 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { let ss = U::state(); // Read the RXDRDY counter. - T::regs().tasks_capture[0].write(|w| unsafe { w.bits(1) }); - let mut end = T::regs().cc[0].read().bits() as usize; + T::regs().tasks_capture(0).write_value(1); + let mut end = T::regs().cc(0).read() as usize; //trace!(" rxdrdy count = {:?}", end); // We've set a compare channel that resets the counter to 0 when it reaches `len*2`. @@ -741,7 +745,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { if start == end { //trace!(" empty"); ss.rx_waker.register(cx.waker()); - r.intenset.write(|w| w.rxdrdy().set_bit()); + r.intenset().write(|w| w.set_rxdrdy(true)); return Poll::Pending; } @@ -759,7 +763,6 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { let buf = s.rx_buf.buf.load(Ordering::Relaxed); Poll::Ready(Ok(unsafe { slice::from_raw_parts(buf.add(start), n) })) }) - .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`. @@ -771,7 +774,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { let s = U::buffered_state(); let mut rx = unsafe { s.rx_buf.reader() }; rx.pop_done(amt); - U::regs().intenset.write(|w| w.rxstarted().set()); + U::regs().intenset().write(|w| w.set_rxstarted(true)); } /// we are ready to read if there is data in the buffer @@ -789,15 +792,14 @@ impl<'a, U: UarteInstance, T: TimerInstance> Drop for BufferedUarteRx<'a, U, T> self.timer.stop(); - r.intenclr.write(|w| { - w.rxdrdy().set_bit(); - w.rxstarted().set_bit(); - w.rxto().set_bit(); - w + r.intenclr().write(|w| { + w.set_rxdrdy(true); + w.set_rxstarted(true); + w.set_rxto(true); }); - r.events_rxto.reset(); - r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); - while r.events_rxto.read().bits() == 0 {} + r.events_rxto().write_value(0); + r.tasks_stoprx().write_value(1); + while r.events_rxto().read() == 0 {} let s = U::buffered_state(); unsafe { s.rx_buf.deinit() } diff --git a/embassy-nrf/src/chips/nrf51.rs b/embassy-nrf/src/chips/nrf51.rs index cc1cbc8a0..fd13ae5c4 100644 --- a/embassy-nrf/src/chips/nrf51.rs +++ b/embassy-nrf/src/chips/nrf51.rs @@ -1,4 +1,4 @@ -pub use nrf51_pac as pac; +pub use nrf_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; @@ -145,12 +145,14 @@ impl_pin!(P0_31, 0, 31); impl_radio!(RADIO, RADIO, RADIO); +impl_wdt!(WDT, WDT, WDT, 0); + embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, UART0, - SPI0_TWI0, - SPI1_TWI1, + TWISPI0, + TWISPI1, GPIOTE, ADC, TIMER0, @@ -160,7 +162,7 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index b51b0c93e..7e72df8fc 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -1,4 +1,4 @@ -pub use nrf52805_pac as pac; +pub use nrf_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; @@ -138,15 +138,15 @@ embassy_hal_internal::peripherals! { EGU1, } -impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); +impl_uarte!(UARTE0, UARTE0, UARTE0); -impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); +impl_spim!(SPI0, SPIM0, SPI0); -impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0); +impl_spis!(SPI0, SPIS0, SPI0); -impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); +impl_twim!(TWI0, TWIM0, TWI0); -impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0); +impl_twis!(TWI0, TWIS0, TWI0); impl_qdec!(QDEC, QDEC, QDEC); @@ -218,15 +218,17 @@ impl_saadc_input!(P0_05, ANALOG_INPUT3); impl_radio!(RADIO, RADIO, RADIO); -impl_egu!(EGU0, EGU0, SWI0_EGU0); -impl_egu!(EGU1, EGU1, SWI1_EGU1); +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); + +impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, - UARTE0_UART0, - TWIM0_TWIS0_TWI0, - SPIM0_SPIS0_SPI0, + UARTE0, + TWI0, + SPI0, GPIOTE, SAADC, TIMER0, @@ -236,12 +238,12 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, - SWI0_EGU0, - SWI1_EGU1, + EGU0_SWI0, + EGU1_SWI1, SWI2, SWI3, SWI4, diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 273098d9b..e388e44e8 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -1,4 +1,4 @@ -pub use nrf52810_pac as pac; +pub use nrf_pac as pac; /// The maximum buffer size that the EasyDMA can send/recv in one operation. pub const EASY_DMA_SIZE: usize = (1 << 10) - 1; @@ -144,15 +144,15 @@ embassy_hal_internal::peripherals! { EGU1, } -impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); +impl_uarte!(UARTE0, UARTE0, UARTE0); -impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); +impl_spim!(SPI0, SPIM0, SPI0); -impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0); +impl_spis!(SPI0, SPIS0, SPI0); -impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); +impl_twim!(TWI0, TWIM0, TWI0); -impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0); +impl_twis!(TWI0, TWIS0, TWI0); impl_pwm!(PWM0, PWM0, PWM0); @@ -244,15 +244,17 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_radio!(RADIO, RADIO, RADIO); -impl_egu!(EGU0, EGU0, SWI0_EGU0); -impl_egu!(EGU1, EGU1, SWI1_EGU1); +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); + +impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, - UARTE0_UART0, - TWIM0_TWIS0_TWI0, - SPIM0_SPIS0_SPI0, + UARTE0, + TWI0, + SPI0, GPIOTE, SAADC, TIMER0, @@ -262,13 +264,13 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, COMP, - SWI0_EGU0, - SWI1_EGU1, + EGU0_SWI0, + EGU1_SWI1, SWI2, SWI3, SWI4, diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 9bce38636..96b8df30b 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -1,4 +1,4 @@ -pub use nrf52811_pac as pac; +pub use nrf_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; @@ -27,8 +27,8 @@ embassy_hal_internal::peripherals! { UARTE0, // SPI/TWI - TWISPI0, - SPI1, + TWI0_SPI1, + SPI0, // SAADC SAADC, @@ -144,17 +144,17 @@ embassy_hal_internal::peripherals! { EGU1, } -impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); +impl_uarte!(UARTE0, UARTE0, UARTE0); -impl_spim!(TWISPI0, SPIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); -impl_spim!(SPI1, SPIM1, SPIM1_SPIS1_SPI1); +impl_spim!(SPI0, SPIM0, SPI0); +impl_spim!(TWI0_SPI1, SPIM1, TWI0_SPI1); -impl_spis!(TWISPI0, SPIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); -impl_spis!(SPI1, SPIS1, SPIM1_SPIS1_SPI1); +impl_spis!(SPI0, SPIS0, SPI0); +impl_spis!(TWI0_SPI1, SPIS1, TWI0_SPI1); -impl_twim!(TWISPI0, TWIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); +impl_twim!(TWI0_SPI1, TWIM0, TWI0_SPI1); -impl_twis!(TWISPI0, TWIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); +impl_twis!(TWI0_SPI1, TWIS0, TWI0_SPI1); impl_pwm!(PWM0, PWM0, PWM0); @@ -246,15 +246,17 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_radio!(RADIO, RADIO, RADIO); -impl_egu!(EGU0, EGU0, SWI0_EGU0); -impl_egu!(EGU1, EGU1, SWI1_EGU1); +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); + +impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, - UARTE0_UART0, - TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0, - SPIM1_SPIS1_SPI1, + UARTE0, + TWI0_SPI1, + SPI0, GPIOTE, SAADC, TIMER0, @@ -264,13 +266,13 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, COMP, - SWI0_EGU0, - SWI1_EGU1, + EGU0_SWI0, + EGU1_SWI1, SWI2, SWI3, SWI4, diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index 2acae2c8f..ad461b153 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -1,4 +1,4 @@ -pub use nrf52820_pac as pac; +pub use nrf_pac as pac; /// The maximum buffer size that the EasyDMA can send/recv in one operation. pub const EASY_DMA_SIZE: usize = (1 << 15) - 1; @@ -145,19 +145,19 @@ embassy_hal_internal::peripherals! { impl_usb!(USBD, USBD, USBD); -impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); +impl_uarte!(UARTE0, UARTE0, UARTE0); -impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); -impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); -impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); -impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); @@ -237,19 +237,21 @@ impl_ppi_channel!(PPI_CH31, 31 => static); impl_radio!(RADIO, RADIO, RADIO); -impl_egu!(EGU0, EGU0, SWI0_EGU0); -impl_egu!(EGU1, EGU1, SWI1_EGU1); -impl_egu!(EGU2, EGU2, SWI2_EGU2); -impl_egu!(EGU3, EGU3, SWI3_EGU3); -impl_egu!(EGU4, EGU4, SWI4_EGU4); -impl_egu!(EGU5, EGU5, SWI5_EGU5); +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, - UARTE0_UART0, - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, - SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + UARTE0, + TWISPI0, + TWISPI1, GPIOTE, TIMER0, TIMER1, @@ -258,17 +260,17 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, COMP, - SWI0_EGU0, - SWI1_EGU1, - SWI2_EGU2, - SWI3_EGU3, - SWI4_EGU4, - SWI5_EGU5, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, TIMER3, USBD, ); diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 94b373ab3..cf2abf23a 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -1,4 +1,4 @@ -pub use nrf52832_pac as pac; +pub use nrf_pac as pac; /// The maximum buffer size that the EasyDMA can send/recv in one operation. pub const EASY_DMA_SIZE: usize = (1 << 8) - 1; @@ -161,23 +161,26 @@ embassy_hal_internal::peripherals! { EGU3, EGU4, EGU5, + + // NFC + NFCT, } -impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); +impl_uarte!(UARTE0, UARTE0, UARTE0); -impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); -impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); +impl_spim!(SPI2, SPIM2, SPI2); -impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); -impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); +impl_spis!(SPI2, SPIS2, SPI2); -impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); -impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); @@ -277,19 +280,21 @@ impl_i2s!(I2S, I2S, I2S); impl_radio!(RADIO, RADIO, RADIO); -impl_egu!(EGU0, EGU0, SWI0_EGU0); -impl_egu!(EGU1, EGU1, SWI1_EGU1); -impl_egu!(EGU2, EGU2, SWI2_EGU2); -impl_egu!(EGU3, EGU3, SWI3_EGU3); -impl_egu!(EGU4, EGU4, SWI4_EGU4); -impl_egu!(EGU5, EGU5, SWI5_EGU5); +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, - UARTE0_UART0, - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, - SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + UARTE0, + TWISPI0, + TWISPI1, NFCT, GPIOTE, SAADC, @@ -300,17 +305,17 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, COMP_LPCOMP, - SWI0_EGU0, - SWI1_EGU1, - SWI2_EGU2, - SWI3_EGU3, - SWI4_EGU4, - SWI5_EGU5, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, TIMER3, TIMER4, PWM0, @@ -318,8 +323,8 @@ embassy_hal_internal::interrupt_mod!( MWU, PWM1, PWM2, - SPIM2_SPIS2_SPI2, + SPI2, RTC2, - FPU, I2S, + FPU, ); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 09cde1ac1..e46eb1d2b 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -1,4 +1,4 @@ -pub use nrf52833_pac as pac; +pub use nrf_pac as pac; /// The maximum buffer size that the EasyDMA can send/recv in one operation. pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; @@ -181,27 +181,30 @@ embassy_hal_internal::peripherals! { EGU3, EGU4, EGU5, + + // NFC + NFCT, } impl_usb!(USBD, USBD, USBD); -impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); +impl_uarte!(UARTE0, UARTE0, UARTE0); impl_uarte!(UARTE1, UARTE1, UARTE1); -impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); -impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); +impl_spim!(SPI2, SPIM2, SPI2); impl_spim!(SPI3, SPIM3, SPIM3); -impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); -impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); +impl_spis!(SPI2, SPIS2, SPI2); -impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); -impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); @@ -319,19 +322,21 @@ impl_i2s!(I2S, I2S, I2S); impl_radio!(RADIO, RADIO, RADIO); -impl_egu!(EGU0, EGU0, SWI0_EGU0); -impl_egu!(EGU1, EGU1, SWI1_EGU1); -impl_egu!(EGU2, EGU2, SWI2_EGU2); -impl_egu!(EGU3, EGU3, SWI3_EGU3); -impl_egu!(EGU4, EGU4, SWI4_EGU4); -impl_egu!(EGU5, EGU5, SWI5_EGU5); +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, - UARTE0_UART0, - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, - SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + UARTE0, + TWISPI0, + TWISPI1, NFCT, GPIOTE, SAADC, @@ -342,17 +347,17 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, COMP_LPCOMP, - SWI0_EGU0, - SWI1_EGU1, - SWI2_EGU2, - SWI3_EGU3, - SWI4_EGU4, - SWI5_EGU5, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, TIMER3, TIMER4, PWM0, @@ -360,12 +365,12 @@ embassy_hal_internal::interrupt_mod!( MWU, PWM1, PWM2, - SPIM2_SPIS2_SPI2, + SPI2, RTC2, + I2S, FPU, USBD, UARTE1, PWM3, SPIM3, - I2S, ); diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 0f3d1b250..88747843d 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -1,4 +1,4 @@ -pub use nrf52840_pac as pac; +pub use nrf_pac as pac; /// The maximum buffer size that the EasyDMA can send/recv in one operation. pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; @@ -184,27 +184,30 @@ embassy_hal_internal::peripherals! { EGU3, EGU4, EGU5, + + // NFC + NFCT, } impl_usb!(USBD, USBD, USBD); -impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); +impl_uarte!(UARTE0, UARTE0, UARTE0); impl_uarte!(UARTE1, UARTE1, UARTE1); -impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); -impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); +impl_spim!(TWISPI0, SPIM0, TWISPI0); +impl_spim!(TWISPI1, SPIM1, TWISPI1); +impl_spim!(SPI2, SPIM2, SPI2); impl_spim!(SPI3, SPIM3, SPIM3); -impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); -impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); +impl_spis!(TWISPI0, SPIS0, TWISPI0); +impl_spis!(TWISPI1, SPIS1, TWISPI1); +impl_spis!(SPI2, SPIS2, SPI2); -impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twim!(TWISPI0, TWIM0, TWISPI0); +impl_twim!(TWISPI1, TWIM1, TWISPI1); -impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, TWISPI0); +impl_twis!(TWISPI1, TWIS1, TWISPI1); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); @@ -324,19 +327,21 @@ impl_i2s!(I2S, I2S, I2S); impl_radio!(RADIO, RADIO, RADIO); -impl_egu!(EGU0, EGU0, SWI0_EGU0); -impl_egu!(EGU1, EGU1, SWI1_EGU1); -impl_egu!(EGU2, EGU2, SWI2_EGU2); -impl_egu!(EGU3, EGU3, SWI3_EGU3); -impl_egu!(EGU4, EGU4, SWI4_EGU4); -impl_egu!(EGU5, EGU5, SWI5_EGU5); +impl_egu!(EGU0, EGU0, EGU0_SWI0); +impl_egu!(EGU1, EGU1, EGU1_SWI1); +impl_egu!(EGU2, EGU2, EGU2_SWI2); +impl_egu!(EGU3, EGU3, EGU3_SWI3); +impl_egu!(EGU4, EGU4, EGU4_SWI4); +impl_egu!(EGU5, EGU5, EGU5_SWI5); + +impl_wdt!(WDT, WDT, WDT, 0); embassy_hal_internal::interrupt_mod!( - POWER_CLOCK, + CLOCK_POWER, RADIO, - UARTE0_UART0, - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, - SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + UARTE0, + TWISPI0, + TWISPI1, NFCT, GPIOTE, SAADC, @@ -347,17 +352,17 @@ embassy_hal_internal::interrupt_mod!( TEMP, RNG, ECB, - CCM_AAR, + AAR_CCM, WDT, RTC1, QDEC, COMP_LPCOMP, - SWI0_EGU0, - SWI1_EGU1, - SWI2_EGU2, - SWI3_EGU3, - SWI4_EGU4, - SWI5_EGU5, + EGU0_SWI0, + EGU1_SWI1, + EGU2_SWI2, + EGU3_SWI3, + EGU4_SWI4, + EGU5_SWI5, TIMER3, TIMER4, PWM0, @@ -365,8 +370,9 @@ embassy_hal_internal::interrupt_mod!( MWU, PWM1, PWM2, - SPIM2_SPIS2_SPI2, + SPI2, RTC2, + I2S, FPU, USBD, UARTE1, @@ -374,5 +380,4 @@ embassy_hal_internal::interrupt_mod!( CRYPTOCELL, PWM3, SPIM3, - I2S, ); diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index 584f6e43c..99cf29487 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -2,228 +2,158 @@ #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { - // The nRF5340 has a secure and non-secure (NS) mode. - // To avoid cfg spam, we remove _ns or _s suffixes here. - - pub use nrf5340_app_pac::NVIC_PRIO_BITS; - - #[cfg(feature="rt")] - #[doc(no_inline)] - pub use nrf5340_app_pac::interrupt; - - #[doc(no_inline)] - pub use nrf5340_app_pac::{ - Interrupt, - Peripherals, - - cache_s as cache, - cachedata_s as cachedata, - cacheinfo_s as cacheinfo, - clock_ns as clock, - comp_ns as comp, - cryptocell_s as cryptocell, - cti_s as cti, - ctrlap_ns as ctrlap, - dcnf_ns as dcnf, - dppic_ns as dppic, - egu0_ns as egu0, - ficr_s as ficr, - fpu_ns as fpu, - gpiote0_s as gpiote, - i2s0_ns as i2s0, - ipc_ns as ipc, - kmu_ns as kmu, - lpcomp_ns as lpcomp, - mutex_ns as mutex, - nfct_ns as nfct, - nvmc_ns as nvmc, - oscillators_ns as oscillators, - p0_ns as p0, - pdm0_ns as pdm, - power_ns as power, - pwm0_ns as pwm0, - qdec0_ns as qdec, - qspi_ns as qspi, - regulators_ns as regulators, - reset_ns as reset, - rtc0_ns as rtc0, - saadc_ns as saadc, - spim0_ns as spim0, - spis0_ns as spis0, - spu_s as spu, - tad_s as tad, - timer0_ns as timer0, - twim0_ns as twim0, - twis0_ns as twis0, - uarte0_ns as uarte0, - uicr_s as uicr, - usbd_ns as usbd, - usbregulator_ns as usbregulator, - vmc_ns as vmc, - wdt0_ns as wdt0, - }; - - /// Non-Secure mode (NS) peripherals - pub mod ns { - #[cfg(feature = "nrf5340-app-ns")] - #[doc(no_inline)] - pub use nrf5340_app_pac::{ - CLOCK_NS as CLOCK, - COMP_NS as COMP, - CTRLAP_NS as CTRLAP, - DCNF_NS as DCNF, - DPPIC_NS as DPPIC, - EGU0_NS as EGU0, - EGU1_NS as EGU1, - EGU2_NS as EGU2, - EGU3_NS as EGU3, - EGU4_NS as EGU4, - EGU5_NS as EGU5, - FPU_NS as FPU, - GPIOTE1_NS as GPIOTE1, - I2S0_NS as I2S0, - IPC_NS as IPC, - KMU_NS as KMU, - LPCOMP_NS as LPCOMP, - MUTEX_NS as MUTEX, - NFCT_NS as NFCT, - NVMC_NS as NVMC, - OSCILLATORS_NS as OSCILLATORS, - P0_NS as P0, - P1_NS as P1, - PDM0_NS as PDM0, - POWER_NS as POWER, - PWM0_NS as PWM0, - PWM1_NS as PWM1, - PWM2_NS as PWM2, - PWM3_NS as PWM3, - QDEC0_NS as QDEC0, - QDEC1_NS as QDEC1, - QSPI_NS as QSPI, - REGULATORS_NS as REGULATORS, - RESET_NS as RESET, - RTC0_NS as RTC0, - RTC1_NS as RTC1, - SAADC_NS as SAADC, - SPIM0_NS as SPIM0, - SPIM1_NS as SPIM1, - SPIM2_NS as SPIM2, - SPIM3_NS as SPIM3, - SPIM4_NS as SPIM4, - SPIS0_NS as SPIS0, - SPIS1_NS as SPIS1, - SPIS2_NS as SPIS2, - SPIS3_NS as SPIS3, - TIMER0_NS as TIMER0, - TIMER1_NS as TIMER1, - TIMER2_NS as TIMER2, - TWIM0_NS as TWIM0, - TWIM1_NS as TWIM1, - TWIM2_NS as TWIM2, - TWIM3_NS as TWIM3, - TWIS0_NS as TWIS0, - TWIS1_NS as TWIS1, - TWIS2_NS as TWIS2, - TWIS3_NS as TWIS3, - UARTE0_NS as UARTE0, - UARTE1_NS as UARTE1, - UARTE2_NS as UARTE2, - UARTE3_NS as UARTE3, - USBD_NS as USBD, - USBREGULATOR_NS as USBREGULATOR, - VMC_NS as VMC, - WDT0_NS as WDT0, - WDT1_NS as WDT1, - }; - } - - /// Secure mode (S) peripherals - pub mod s { - #[cfg(feature = "nrf5340-app-s")] - #[doc(no_inline)] - pub use nrf5340_app_pac::{ - CACHEDATA_S as CACHEDATA, - CACHEINFO_S as CACHEINFO, - CACHE_S as CACHE, - CLOCK_S as CLOCK, - COMP_S as COMP, - CRYPTOCELL_S as CRYPTOCELL, - CTI_S as CTI, - CTRLAP_S as CTRLAP, - DCNF_S as DCNF, - DPPIC_S as DPPIC, - EGU0_S as EGU0, - EGU1_S as EGU1, - EGU2_S as EGU2, - EGU3_S as EGU3, - EGU4_S as EGU4, - EGU5_S as EGU5, - FICR_S as FICR, - FPU_S as FPU, - GPIOTE0_S as GPIOTE0, - I2S0_S as I2S0, - IPC_S as IPC, - KMU_S as KMU, - LPCOMP_S as LPCOMP, - MUTEX_S as MUTEX, - NFCT_S as NFCT, - NVMC_S as NVMC, - OSCILLATORS_S as OSCILLATORS, - P0_S as P0, - P1_S as P1, - PDM0_S as PDM0, - POWER_S as POWER, - PWM0_S as PWM0, - PWM1_S as PWM1, - PWM2_S as PWM2, - PWM3_S as PWM3, - QDEC0_S as QDEC0, - QDEC1_S as QDEC1, - QSPI_S as QSPI, - REGULATORS_S as REGULATORS, - RESET_S as RESET, - RTC0_S as RTC0, - RTC1_S as RTC1, - SAADC_S as SAADC, - SPIM0_S as SPIM0, - SPIM1_S as SPIM1, - SPIM2_S as SPIM2, - SPIM3_S as SPIM3, - SPIM4_S as SPIM4, - SPIS0_S as SPIS0, - SPIS1_S as SPIS1, - SPIS2_S as SPIS2, - SPIS3_S as SPIS3, - SPU_S as SPU, - TAD_S as TAD, - TIMER0_S as TIMER0, - TIMER1_S as TIMER1, - TIMER2_S as TIMER2, - TWIM0_S as TWIM0, - TWIM1_S as TWIM1, - TWIM2_S as TWIM2, - TWIM3_S as TWIM3, - TWIS0_S as TWIS0, - TWIS1_S as TWIS1, - TWIS2_S as TWIS2, - TWIS3_S as TWIS3, - UARTE0_S as UARTE0, - UARTE1_S as UARTE1, - UARTE2_S as UARTE2, - UARTE3_S as UARTE3, - UICR_S as UICR, - USBD_S as USBD, - USBREGULATOR_S as USBREGULATOR, - VMC_S as VMC, - WDT0_S as WDT0, - WDT1_S as WDT1, - }; - } + pub use nrf_pac::*; #[cfg(feature = "_ns")] - pub use ns::*; + #[doc(no_inline)] + pub use nrf_pac::{ + CLOCK_NS as CLOCK, + COMP_NS as COMP, + CTRLAP_NS as CTRLAP, + DCNF_NS as DCNF, + DPPIC_NS as DPPIC, + EGU0_NS as EGU0, + EGU1_NS as EGU1, + EGU2_NS as EGU2, + EGU3_NS as EGU3, + EGU4_NS as EGU4, + EGU5_NS as EGU5, + FPU_NS as FPU, + GPIOTE1_NS as GPIOTE1, + I2S0_NS as I2S0, + IPC_NS as IPC, + KMU_NS as KMU, + LPCOMP_NS as LPCOMP, + MUTEX_NS as MUTEX, + NFCT_NS as NFCT, + NVMC_NS as NVMC, + OSCILLATORS_NS as OSCILLATORS, + P0_NS as P0, + P1_NS as P1, + PDM0_NS as PDM0, + POWER_NS as POWER, + PWM0_NS as PWM0, + PWM1_NS as PWM1, + PWM2_NS as PWM2, + PWM3_NS as PWM3, + QDEC0_NS as QDEC0, + QDEC1_NS as QDEC1, + QSPI_NS as QSPI, + REGULATORS_NS as REGULATORS, + RESET_NS as RESET, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SAADC_NS as SAADC, + SPIM0_NS as SPIM0, + SPIM1_NS as SPIM1, + SPIM2_NS as SPIM2, + SPIM3_NS as SPIM3, + SPIM4_NS as SPIM4, + SPIS0_NS as SPIS0, + SPIS1_NS as SPIS1, + SPIS2_NS as SPIS2, + SPIS3_NS as SPIS3, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIM1_NS as TWIM1, + TWIM2_NS as TWIM2, + TWIM3_NS as TWIM3, + TWIS0_NS as TWIS0, + TWIS1_NS as TWIS1, + TWIS2_NS as TWIS2, + TWIS3_NS as TWIS3, + UARTE0_NS as UARTE0, + UARTE1_NS as UARTE1, + UARTE2_NS as UARTE2, + UARTE3_NS as UARTE3, + USBD_NS as USBD, + USBREGULATOR_NS as USBREGULATOR, + VMC_NS as VMC, + WDT0_NS as WDT0, + WDT1_NS as WDT1, + }; + #[cfg(feature = "_s")] - pub use s::*; + #[doc(no_inline)] + pub use nrf_pac::{ + CACHEDATA_S as CACHEDATA, + CACHEINFO_S as CACHEINFO, + CACHE_S as CACHE, + CLOCK_S as CLOCK, + COMP_S as COMP, + CRYPTOCELL_S as CRYPTOCELL, + CTI_S as CTI, + CTRLAP_S as CTRLAP, + DCNF_S as DCNF, + DPPIC_S as DPPIC, + EGU0_S as EGU0, + EGU1_S as EGU1, + EGU2_S as EGU2, + EGU3_S as EGU3, + EGU4_S as EGU4, + EGU5_S as EGU5, + FICR_S as FICR, + FPU_S as FPU, + GPIOTE0_S as GPIOTE0, + I2S0_S as I2S0, + IPC_S as IPC, + KMU_S as KMU, + LPCOMP_S as LPCOMP, + MUTEX_S as MUTEX, + NFCT_S as NFCT, + NVMC_S as NVMC, + OSCILLATORS_S as OSCILLATORS, + P0_S as P0, + P1_S as P1, + PDM0_S as PDM0, + POWER_S as POWER, + PWM0_S as PWM0, + PWM1_S as PWM1, + PWM2_S as PWM2, + PWM3_S as PWM3, + QDEC0_S as QDEC0, + QDEC1_S as QDEC1, + QSPI_S as QSPI, + REGULATORS_S as REGULATORS, + RESET_S as RESET, + RTC0_S as RTC0, + RTC1_S as RTC1, + SAADC_S as SAADC, + SPIM0_S as SPIM0, + SPIM1_S as SPIM1, + SPIM2_S as SPIM2, + SPIM3_S as SPIM3, + SPIM4_S as SPIM4, + SPIS0_S as SPIS0, + SPIS1_S as SPIS1, + SPIS2_S as SPIS2, + SPIS3_S as SPIS3, + SPU_S as SPU, + TAD_S as TAD, + TIMER0_S as TIMER0, + TIMER1_S as TIMER1, + TIMER2_S as TIMER2, + TWIM0_S as TWIM0, + TWIM1_S as TWIM1, + TWIM2_S as TWIM2, + TWIM3_S as TWIM3, + TWIS0_S as TWIS0, + TWIS1_S as TWIS1, + TWIS2_S as TWIS2, + TWIS3_S as TWIS3, + UARTE0_S as UARTE0, + UARTE1_S as UARTE1, + UARTE2_S as UARTE2, + UARTE3_S as UARTE3, + UICR_S as UICR, + USBD_S as USBD, + USBREGULATOR_S as USBREGULATOR, + VMC_S as VMC, + WDT0_S as WDT0, + WDT1_S as WDT1, + }; } /// The maximum buffer size that the EasyDMA can send/recv in one operation. @@ -241,16 +171,21 @@ embassy_hal_internal::peripherals! { RTC1, // WDT - WDT, + WDT0, + WDT1, // NVMC NVMC, + // NFC + NFCT, + // UARTE, TWI & SPI SERIAL0, SERIAL1, SERIAL2, SERIAL3, + SPIM4, // SAADC SAADC, @@ -327,8 +262,13 @@ embassy_hal_internal::peripherals! { PPI_GROUP4, PPI_GROUP5, + // IPC + IPC, + // GPIO port 0 + #[cfg(feature = "lfxo-pins-as-gpio")] P0_00, + #[cfg(feature = "lfxo-pins-as-gpio")] P0_01, #[cfg(feature = "nfc-pins-as-gpio")] P0_02, @@ -390,6 +330,8 @@ embassy_hal_internal::peripherals! { EGU5, } +impl_ipc!(IPC, IPC, IPC); + impl_usb!(USBD, USBD, USBD); impl_uarte!(SERIAL0, UARTE0, SERIAL0); @@ -401,6 +343,7 @@ impl_spim!(SERIAL0, SPIM0, SERIAL0); impl_spim!(SERIAL1, SPIM1, SERIAL1); impl_spim!(SERIAL2, SPIM2, SERIAL2); impl_spim!(SERIAL3, SPIM3, SERIAL3); +impl_spim!(SPIM4, SPIM4, SPIM4); impl_spis!(SERIAL0, SPIS0, SERIAL0); impl_spis!(SERIAL1, SPIS1, SERIAL1); @@ -433,7 +376,9 @@ impl_pdm!(PDM0, PDM0, PDM0); impl_qdec!(QDEC0, QDEC0, QDEC0); impl_qdec!(QDEC1, QDEC1, QDEC1); +#[cfg(feature = "lfxo-pins-as-gpio")] impl_pin!(P0_00, 0, 0); +#[cfg(feature = "lfxo-pins-as-gpio")] impl_pin!(P0_01, 0, 1); #[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_02, 0, 2); @@ -534,6 +479,9 @@ impl_egu!(EGU3, EGU3, EGU3); impl_egu!(EGU4, EGU4, EGU4); impl_egu!(EGU5, EGU5, EGU5); +impl_wdt!(WDT0, WDT0, WDT0, 0); +impl_wdt!(WDT1, WDT1, WDT1, 1); + embassy_hal_internal::interrupt_mod!( FPU, CACHE, diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index d772c9a49..d4c3e5353 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -2,77 +2,25 @@ #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { - // The nRF5340 has a secure and non-secure (NS) mode. - // To avoid cfg spam, we remove _ns or _s suffixes here. - - pub use nrf5340_net_pac::NVIC_PRIO_BITS; - - #[cfg(feature="rt")] - #[doc(no_inline)] - pub use nrf5340_net_pac::interrupt; + pub use nrf_pac::*; #[doc(no_inline)] - pub use nrf5340_net_pac::{ - Interrupt, - Peripherals, - - aar_ns as aar, - acl_ns as acl, - appmutex_ns as appmutex, - ccm_ns as ccm, - clock_ns as clock, - cti_ns as cti, - ctrlap_ns as ctrlap, - dcnf_ns as dcnf, - dppic_ns as dppic, - ecb_ns as ecb, - egu0_ns as egu0, - ficr_ns as ficr, - gpiote_ns as gpiote, - ipc_ns as ipc, - nvmc_ns as nvmc, - p0_ns as p0, - power_ns as power, - radio_ns as radio, - reset_ns as reset, - rng_ns as rng, - rtc0_ns as rtc0, - spim0_ns as spim0, - spis0_ns as spis0, - swi0_ns as swi0, - temp_ns as temp, - timer0_ns as timer0, - twim0_ns as twim0, - twis0_ns as twis0, - uarte0_ns as uarte0, - uicr_ns as uicr, - vmc_ns as vmc, - vreqctrl_ns as vreqctrl, - wdt_ns as wdt, - + pub use nrf_pac::{ AAR_NS as AAR, ACL_NS as ACL, APPMUTEX_NS as APPMUTEX, APPMUTEX_S as APPMUTEX_S, - CBP as CBP, CCM_NS as CCM, CLOCK_NS as CLOCK, - CPUID as CPUID, CTI_NS as CTI, CTRLAP_NS as CTRLAP, - DCB as DCB, DCNF_NS as DCNF, DPPIC_NS as DPPIC, - DWT as DWT, ECB_NS as ECB, EGU0_NS as EGU0, FICR_NS as FICR, - FPB as FPB, GPIOTE_NS as GPIOTE, IPC_NS as IPC, - ITM as ITM, - MPU as MPU, - NVIC as NVIC, NVMC_NS as NVMC, P0_NS as P0, P1_NS as P1, @@ -82,19 +30,16 @@ pub mod pac { RNG_NS as RNG, RTC0_NS as RTC0, RTC1_NS as RTC1, - SCB as SCB, SPIM0_NS as SPIM0, SPIS0_NS as SPIS0, SWI0_NS as SWI0, SWI1_NS as SWI1, SWI2_NS as SWI2, SWI3_NS as SWI3, - SYST as SYST, TEMP_NS as TEMP, TIMER0_NS as TIMER0, TIMER1_NS as TIMER1, TIMER2_NS as TIMER2, - TPIU as TPIU, TWIM0_NS as TWIM0, TWIS0_NS as TWIS0, UARTE0_NS as UARTE0, @@ -103,7 +48,6 @@ pub mod pac { VREQCTRL_NS as VREQCTRL, WDT_NS as WDT, }; - } /// The maximum buffer size that the EasyDMA can send/recv in one operation. @@ -197,6 +141,9 @@ embassy_hal_internal::peripherals! { PPI_GROUP4, PPI_GROUP5, + // IPC + IPC, + // GPIO port 0 P0_00, P0_01, @@ -254,8 +201,13 @@ embassy_hal_internal::peripherals! { // EGU EGU0, + + // TEMP + TEMP, } +impl_ipc!(IPC, IPC, IPC); + impl_uarte!(SERIAL0, UARTE0, SERIAL0); impl_spim!(SERIAL0, SPIM0, SERIAL0); impl_spis!(SERIAL0, SPIS0, SERIAL0); @@ -355,6 +307,8 @@ impl_radio!(RADIO, RADIO, RADIO); impl_egu!(EGU0, EGU0, EGU0); +impl_wdt!(WDT, WDT, WDT, 0); + embassy_hal_internal::interrupt_mod!( CLOCK_POWER, RADIO, diff --git a/embassy-nrf/src/chips/nrf54l15_app.rs b/embassy-nrf/src/chips/nrf54l15_app.rs new file mode 100644 index 000000000..b133eb565 --- /dev/null +++ b/embassy-nrf/src/chips/nrf54l15_app.rs @@ -0,0 +1,346 @@ +/// Peripheral Access Crate +#[allow(unused_imports)] +#[rustfmt::skip] +pub mod pac { + pub use nrf_pac::*; + + #[cfg(feature = "_ns")] + #[doc(no_inline)] + pub use nrf_pac::{ + FICR_NS as FICR, + DPPIC00_NS as DPPIC00, + PPIB00_NS as PPIB00, + PPIB01_NS as PPIB01, + AAR00_NS as AAR00, + CCM00_NS as CCM00, + ECB00_NS as ECB00, + SPIM00_NS as SPIM00, + SPIS00_NS as SPIS00, + UARTE00_NS as UARTE00, + VPR00_NS as VPR00, + P2_NS as P2, + CTRLAP_NS as CTRLAP, + TAD_NS as TAD, + TIMER00_NS as TIMER00, + DPPIC10_NS as DPPIC10, + PPIB10_NS as PPIB10, + PPIB11_NS as PPIB11, + TIMER10_NS as TIMER10, + RTC10_NS as RTC10, + EGU10_NS as EGU10, + RADIO_NS as RADIO, + DPPIC20_NS as DPPIC20, + PPIB20_NS as PPIB20, + PPIB21_NS as PPIB21, + PPIB22_NS as PPIB22, + SPIM20_NS as SPIM20, + SPIS20_NS as SPIS20, + TWIM20_NS as TWIM20, + TWIS20_NS as TWIS20, + UARTE20_NS as UARTE20, + SPIM21_NS as SPIM21, + SPIS21_NS as SPIS21, + TWIM21_NS as TWIM21, + TWIS21_NS as TWIS21, + UARTE21_NS as UARTE21, + SPIM22_NS as SPIM22, + SPIS22_NS as SPIS22, + TWIM22_NS as TWIM22, + TWIS22_NS as TWIS22, + UARTE22_NS as UARTE22, + EGU20_NS as EGU20, + TIMER20_NS as TIMER20, + TIMER21_NS as TIMER21, + TIMER22_NS as TIMER22, + TIMER23_NS as TIMER23, + TIMER24_NS as TIMER24, + MEMCONF_NS as MEMCONF, + PDM20_NS as PDM20, + PDM21_NS as PDM21, + PWM20_NS as PWM20, + PWM21_NS as PWM21, + PWM22_NS as PWM22, + SAADC_NS as SAADC, + NFCT_NS as NFCT, + TEMP_NS as TEMP, + P1_NS as P1, + GPIOTE20_NS as GPIOTE20, + I2S20_NS as I2S20, + QDEC20_NS as QDEC20, + QDEC21_NS as QDEC21, + GRTC_NS as GRTC, + DPPIC30_NS as DPPIC30, + PPIB30_NS as PPIB30, + SPIM30_NS as SPIM30, + SPIS30_NS as SPIS30, + TWIM30_NS as TWIM30, + TWIS30_NS as TWIS30, + UARTE30_NS as UARTE30, + RTC30_NS as RTC30, + COMP_NS as COMP, + LPCOMP_NS as LPCOMP, + WDT31_NS as WDT31, + P0_NS as P0, + GPIOTE30_NS as GPIOTE30, + CLOCK_NS as CLOCK, + POWER_NS as POWER, + RESET_NS as RESET, + OSCILLATORS_NS as OSCILLATORS, + REGULATORS_NS as REGULATORS, + TPIU_NS as TPIU, + ETM_NS as ETM, + }; + + #[cfg(feature = "_s")] + #[doc(no_inline)] + pub use nrf_pac::{ + SICR_S as SICR, + ICACHEDATA_S as ICACHEDATA, + ICACHEINFO_S as ICACHEINFO, + SWI00_S as SWI00, + SWI01_S as SWI01, + SWI02_S as SWI02, + SWI03_S as SWI03, + SPU00_S as SPU00, + MPC00_S as MPC00, + DPPIC00_S as DPPIC00, + PPIB00_S as PPIB00, + PPIB01_S as PPIB01, + KMU_S as KMU, + AAR00_S as AAR00, + CCM00_S as CCM00, + ECB00_S as ECB00, + CRACEN_S as CRACEN, + SPIM00_S as SPIM00, + SPIS00_S as SPIS00, + UARTE00_S as UARTE00, + GLITCHDET_S as GLITCHDET, + RRAMC_S as RRAMC, + VPR00_S as VPR00, + P2_S as P2, + CTRLAP_S as CTRLAP, + TAD_S as TAD, + TIMER00_S as TIMER00, + SPU10_S as SPU10, + DPPIC10_S as DPPIC10, + PPIB10_S as PPIB10, + PPIB11_S as PPIB11, + TIMER10_S as TIMER10, + RTC10_S as RTC10, + EGU10_S as EGU10, + RADIO_S as RADIO, + SPU20_S as SPU20, + DPPIC20_S as DPPIC20, + PPIB20_S as PPIB20, + PPIB21_S as PPIB21, + PPIB22_S as PPIB22, + SPIM20_S as SPIM20, + SPIS20_S as SPIS20, + TWIM20_S as TWIM20, + TWIS20_S as TWIS20, + UARTE20_S as UARTE20, + SPIM21_S as SPIM21, + SPIS21_S as SPIS21, + TWIM21_S as TWIM21, + TWIS21_S as TWIS21, + UARTE21_S as UARTE21, + SPIM22_S as SPIM22, + SPIS22_S as SPIS22, + TWIM22_S as TWIM22, + TWIS22_S as TWIS22, + UARTE22_S as UARTE22, + EGU20_S as EGU20, + TIMER20_S as TIMER20, + TIMER21_S as TIMER21, + TIMER22_S as TIMER22, + TIMER23_S as TIMER23, + TIMER24_S as TIMER24, + MEMCONF_S as MEMCONF, + PDM20_S as PDM20, + PDM21_S as PDM21, + PWM20_S as PWM20, + PWM21_S as PWM21, + PWM22_S as PWM22, + SAADC_S as SAADC, + NFCT_S as NFCT, + TEMP_S as TEMP, + P1_S as P1, + GPIOTE20_S as GPIOTE20, + TAMPC_S as TAMPC, + I2S20_S as I2S20, + QDEC20_S as QDEC20, + QDEC21_S as QDEC21, + GRTC_S as GRTC, + SPU30_S as SPU30, + DPPIC30_S as DPPIC30, + PPIB30_S as PPIB30, + SPIM30_S as SPIM30, + SPIS30_S as SPIS30, + TWIM30_S as TWIM30, + TWIS30_S as TWIS30, + UARTE30_S as UARTE30, + RTC30_S as RTC30, + COMP_S as COMP, + LPCOMP_S as LPCOMP, + WDT30_S as WDT30, + WDT31_S as WDT31, + P0_S as P0, + GPIOTE30_S as GPIOTE30, + CLOCK_S as CLOCK, + POWER_S as POWER, + RESET_S as RESET, + OSCILLATORS_S as OSCILLATORS, + REGULATORS_S as REGULATORS, + CRACENCORE_S as CRACENCORE, + CPUC_S as CPUC, + ICACHE_S as ICACHE, + }; +} + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; +//pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; + +//pub const FLASH_SIZE: usize = 1024 * 1024; + +embassy_hal_internal::peripherals! { + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + + // GPIO port 1 + P1_00, + P1_01, + P1_02, + P1_03, + P1_04, + P1_05, + P1_06, + P1_07, + P1_08, + P1_09, + P1_10, + P1_11, + P1_12, + P1_13, + P1_14, + P1_15, + P1_16, + + + // GPIO port 2 + P2_00, + P2_01, + P2_02, + P2_03, + P2_04, + P2_05, + P2_06, + P2_07, + P2_08, + P2_09, + P2_10, +} + +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!(P1_00, 1, 0); +impl_pin!(P1_01, 1, 1); +impl_pin!(P1_02, 1, 2); +impl_pin!(P1_03, 1, 3); +impl_pin!(P1_04, 1, 4); +impl_pin!(P1_05, 1, 5); +impl_pin!(P1_06, 1, 6); +impl_pin!(P1_07, 1, 7); +impl_pin!(P1_08, 1, 8); +impl_pin!(P1_09, 1, 9); +impl_pin!(P1_10, 1, 10); +impl_pin!(P1_11, 1, 11); +impl_pin!(P1_12, 1, 12); +impl_pin!(P1_13, 1, 13); +impl_pin!(P1_14, 1, 14); +impl_pin!(P1_15, 1, 15); +impl_pin!(P1_16, 1, 16); + +impl_pin!(P2_00, 2, 0); +impl_pin!(P2_01, 2, 1); +impl_pin!(P2_02, 2, 2); +impl_pin!(P2_03, 2, 3); +impl_pin!(P2_04, 2, 4); +impl_pin!(P2_05, 2, 5); +impl_pin!(P2_06, 2, 6); +impl_pin!(P2_07, 2, 7); +impl_pin!(P2_08, 2, 8); +impl_pin!(P2_09, 2, 9); +impl_pin!(P2_10, 2, 10); + +embassy_hal_internal::interrupt_mod!( + SWI00, + SWI01, + SWI02, + SWI03, + SPU00, + MPC00, + AAR00_CCM00, + ECB00, + CRACEN, + SERIAL00, + RRAMC, + VPR00, + CTRLAP, + TIMER00, + SPU10, + TIMER10, + RTC10, + EGU10, + RADIO_0, + RADIO_1, + SPU20, + SERIAL20, + SERIAL21, + SERIAL22, + EGU20, + TIMER20, + TIMER21, + TIMER22, + TIMER23, + TIMER24, + PDM20, + PDM21, + PWM20, + PWM21, + PWM22, + SAADC, + NFCT, + TEMP, + GPIOTE20_0, + GPIOTE20_1, + TAMPC, + I2S20, + QDEC20, + QDEC21, + GRTC_0, + GRTC_1, + GRTC_2, + GRTC_3, + SPU30, + SERIAL30, + RTC30, + COMP_LPCOMP, + WDT30, + WDT31, + GPIOTE30_0, + GPIOTE30_1, + CLOCK_POWER, +); diff --git a/embassy-nrf/src/chips/nrf9120.rs b/embassy-nrf/src/chips/nrf9120.rs index b53510118..e8ddbf86f 100644 --- a/embassy-nrf/src/chips/nrf9120.rs +++ b/embassy-nrf/src/chips/nrf9120.rs @@ -2,177 +2,124 @@ #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { - // The nRF9120 has a secure and non-secure (NS) mode. - // To avoid cfg spam, we remove _ns or _s suffixes here. - - pub use nrf9120_pac::NVIC_PRIO_BITS; - - #[cfg(feature="rt")] - #[doc(no_inline)] - pub use nrf9120_pac::interrupt; - - #[doc(no_inline)] - pub use nrf9120_pac::{ - Interrupt, - - cc_host_rgf_s as cc_host_rgf, - clock_ns as clock, - cryptocell_s as cryptocell, - ctrl_ap_peri_s as ctrl_ap_peri, - dppic_ns as dppic, - egu0_ns as egu0, - ficr_s as ficr, - fpu_ns as fpu, - gpiote0_s as gpiote, - i2s_ns as i2s, - ipc_ns as ipc, - kmu_ns as kmu, - nvmc_ns as nvmc, - p0_ns as p0, - pdm_ns as pdm, - power_ns as power, - pwm0_ns as pwm0, - regulators_ns as regulators, - rtc0_ns as rtc0, - saadc_ns as saadc, - spim0_ns as spim0, - spis0_ns as spis0, - spu_s as spu, - tad_s as tad, - timer0_ns as timer0, - twim0_ns as twim0, - twis0_ns as twis0, - uarte0_ns as uarte0, - uicr_s as uicr, - vmc_ns as vmc, - wdt_ns as wdt, - }; - - /// Non-Secure mode (NS) peripherals - pub mod ns { - #[doc(no_inline)] - pub use nrf9120_pac::{ - CLOCK_NS as CLOCK, - DPPIC_NS as DPPIC, - EGU0_NS as EGU0, - EGU1_NS as EGU1, - EGU2_NS as EGU2, - EGU3_NS as EGU3, - EGU4_NS as EGU4, - EGU5_NS as EGU5, - FPU_NS as FPU, - GPIOTE1_NS as GPIOTE1, - I2S_NS as I2S, - IPC_NS as IPC, - KMU_NS as KMU, - NVMC_NS as NVMC, - P0_NS as P0, - PDM_NS as PDM, - POWER_NS as POWER, - PWM0_NS as PWM0, - PWM1_NS as PWM1, - PWM2_NS as PWM2, - PWM3_NS as PWM3, - REGULATORS_NS as REGULATORS, - RTC0_NS as RTC0, - RTC1_NS as RTC1, - SAADC_NS as SAADC, - SPIM0_NS as SPIM0, - SPIM1_NS as SPIM1, - SPIM2_NS as SPIM2, - SPIM3_NS as SPIM3, - SPIS0_NS as SPIS0, - SPIS1_NS as SPIS1, - SPIS2_NS as SPIS2, - SPIS3_NS as SPIS3, - TIMER0_NS as TIMER0, - TIMER1_NS as TIMER1, - TIMER2_NS as TIMER2, - TWIM0_NS as TWIM0, - TWIM1_NS as TWIM1, - TWIM2_NS as TWIM2, - TWIM3_NS as TWIM3, - TWIS0_NS as TWIS0, - TWIS1_NS as TWIS1, - TWIS2_NS as TWIS2, - TWIS3_NS as TWIS3, - UARTE0_NS as UARTE0, - UARTE1_NS as UARTE1, - UARTE2_NS as UARTE2, - UARTE3_NS as UARTE3, - VMC_NS as VMC, - WDT_NS as WDT, - }; - } - - /// Secure mode (S) peripherals - pub mod s { - #[doc(no_inline)] - pub use nrf9120_pac::{ - CC_HOST_RGF_S as CC_HOST_RGF, - CLOCK_S as CLOCK, - CRYPTOCELL_S as CRYPTOCELL, - CTRL_AP_PERI_S as CTRL_AP_PERI, - DPPIC_S as DPPIC, - EGU0_S as EGU0, - EGU1_S as EGU1, - EGU2_S as EGU2, - EGU3_S as EGU3, - EGU4_S as EGU4, - EGU5_S as EGU5, - FICR_S as FICR, - FPU as FPU, - GPIOTE0_S as GPIOTE0, - I2S_S as I2S, - IPC_S as IPC, - KMU_S as KMU, - NVMC_S as NVMC, - P0_S as P0, - PDM_S as PDM, - POWER_S as POWER, - PWM0_S as PWM0, - PWM1_S as PWM1, - PWM2_S as PWM2, - PWM3_S as PWM3, - REGULATORS_S as REGULATORS, - RTC0_S as RTC0, - RTC1_S as RTC1, - SAADC_S as SAADC, - SPIM0_S as SPIM0, - SPIM1_S as SPIM1, - SPIM2_S as SPIM2, - SPIM3_S as SPIM3, - SPIS0_S as SPIS0, - SPIS1_S as SPIS1, - SPIS2_S as SPIS2, - SPIS3_S as SPIS3, - SPU_S as SPU, - TAD_S as TAD, - TIMER0_S as TIMER0, - TIMER1_S as TIMER1, - TIMER2_S as TIMER2, - TWIM0_S as TWIM0, - TWIM1_S as TWIM1, - TWIM2_S as TWIM2, - TWIM3_S as TWIM3, - TWIS0_S as TWIS0, - TWIS1_S as TWIS1, - TWIS2_S as TWIS2, - TWIS3_S as TWIS3, - UARTE0_S as UARTE0, - UARTE1_S as UARTE1, - UARTE2_S as UARTE2, - UARTE3_S as UARTE3, - UICR_S as UICR, - VMC_S as VMC, - WDT_S as WDT, - }; - } + pub use nrf_pac::*; #[cfg(feature = "_ns")] - pub use ns::*; + #[doc(no_inline)] + pub use nrf_pac::{ + CLOCK_NS as CLOCK, + DPPIC_NS as DPPIC, + EGU0_NS as EGU0, + EGU1_NS as EGU1, + EGU2_NS as EGU2, + EGU3_NS as EGU3, + EGU4_NS as EGU4, + EGU5_NS as EGU5, + FPU_NS as FPU, + GPIOTE1_NS as GPIOTE1, + I2S_NS as I2S, + IPC_NS as IPC, + KMU_NS as KMU, + NVMC_NS as NVMC, + P0_NS as P0, + PDM_NS as PDM, + POWER_NS as POWER, + PWM0_NS as PWM0, + PWM1_NS as PWM1, + PWM2_NS as PWM2, + PWM3_NS as PWM3, + REGULATORS_NS as REGULATORS, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SAADC_NS as SAADC, + SPIM0_NS as SPIM0, + SPIM1_NS as SPIM1, + SPIM2_NS as SPIM2, + SPIM3_NS as SPIM3, + SPIS0_NS as SPIS0, + SPIS1_NS as SPIS1, + SPIS2_NS as SPIS2, + SPIS3_NS as SPIS3, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIM1_NS as TWIM1, + TWIM2_NS as TWIM2, + TWIM3_NS as TWIM3, + TWIS0_NS as TWIS0, + TWIS1_NS as TWIS1, + TWIS2_NS as TWIS2, + TWIS3_NS as TWIS3, + UARTE0_NS as UARTE0, + UARTE1_NS as UARTE1, + UARTE2_NS as UARTE2, + UARTE3_NS as UARTE3, + VMC_NS as VMC, + WDT_NS as WDT, + }; + #[cfg(feature = "_s")] - pub use s::*; + #[doc(no_inline)] + pub use nrf_pac::{ + CC_HOST_RGF_S as CC_HOST_RGF, + CLOCK_S as CLOCK, + CRYPTOCELL_S as CRYPTOCELL, + CTRL_AP_PERI_S as CTRL_AP_PERI, + DPPIC_S as DPPIC, + EGU0_S as EGU0, + EGU1_S as EGU1, + EGU2_S as EGU2, + EGU3_S as EGU3, + EGU4_S as EGU4, + EGU5_S as EGU5, + FICR_S as FICR, + FPU_NS as FPU, + GPIOTE0_S as GPIOTE0, + I2S_S as I2S, + IPC_S as IPC, + KMU_S as KMU, + NVMC_S as NVMC, + P0_S as P0, + PDM_S as PDM, + POWER_S as POWER, + PWM0_S as PWM0, + PWM1_S as PWM1, + PWM2_S as PWM2, + PWM3_S as PWM3, + REGULATORS_S as REGULATORS, + RTC0_S as RTC0, + RTC1_S as RTC1, + SAADC_S as SAADC, + SPIM0_S as SPIM0, + SPIM1_S as SPIM1, + SPIM2_S as SPIM2, + SPIM3_S as SPIM3, + SPIS0_S as SPIS0, + SPIS1_S as SPIS1, + SPIS2_S as SPIS2, + SPIS3_S as SPIS3, + SPU_S as SPU, + TAD_S as TAD, + TIMER0_S as TIMER0, + TIMER1_S as TIMER1, + TIMER2_S as TIMER2, + TWIM0_S as TWIM0, + TWIM1_S as TWIM1, + TWIM2_S as TWIM2, + TWIM3_S as TWIM3, + TWIS0_S as TWIS0, + TWIS1_S as TWIS1, + TWIS2_S as TWIS2, + TWIS3_S as TWIS3, + UARTE0_S as UARTE0, + UARTE1_S as UARTE1, + UARTE2_S as UARTE2, + UARTE3_S as UARTE3, + UICR_S as UICR, + VMC_S as VMC, + WDT_S as WDT, + }; } /// The maximum buffer size that the EasyDMA can send/recv in one operation. @@ -293,30 +240,30 @@ embassy_hal_internal::peripherals! { EGU5, } -impl_uarte!(SERIAL0, UARTE0, SPIM0_SPIS0_TWIM0_TWIS0_UARTE0); -impl_uarte!(SERIAL1, UARTE1, SPIM1_SPIS1_TWIM1_TWIS1_UARTE1); -impl_uarte!(SERIAL2, UARTE2, SPIM2_SPIS2_TWIM2_TWIS2_UARTE2); -impl_uarte!(SERIAL3, UARTE3, SPIM3_SPIS3_TWIM3_TWIS3_UARTE3); +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); -impl_spim!(SERIAL0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_UARTE0); -impl_spim!(SERIAL1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_UARTE1); -impl_spim!(SERIAL2, SPIM2, SPIM2_SPIS2_TWIM2_TWIS2_UARTE2); -impl_spim!(SERIAL3, SPIM3, SPIM3_SPIS3_TWIM3_TWIS3_UARTE3); +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); -impl_spis!(SERIAL0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_UARTE0); -impl_spis!(SERIAL1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_UARTE1); -impl_spis!(SERIAL2, SPIS2, SPIM2_SPIS2_TWIM2_TWIS2_UARTE2); -impl_spis!(SERIAL3, SPIS3, SPIM3_SPIS3_TWIM3_TWIS3_UARTE3); +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); -impl_twim!(SERIAL0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_UARTE0); -impl_twim!(SERIAL1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_UARTE1); -impl_twim!(SERIAL2, TWIM2, SPIM2_SPIS2_TWIM2_TWIS2_UARTE2); -impl_twim!(SERIAL3, TWIM3, SPIM3_SPIS3_TWIM3_TWIS3_UARTE3); +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); -impl_twis!(SERIAL0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_UARTE0); -impl_twis!(SERIAL1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_UARTE1); -impl_twis!(SERIAL2, TWIS2, SPIM2_SPIS2_TWIM2_TWIS2_UARTE2); -impl_twis!(SERIAL3, TWIS3, SPIM3_SPIS3_TWIM3_TWIS3_UARTE3); +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); @@ -395,13 +342,15 @@ impl_egu!(EGU3, EGU3, EGU3); impl_egu!(EGU4, EGU4, EGU4); impl_egu!(EGU5, EGU5, EGU5); +impl_wdt!(WDT, WDT, WDT, 0); + embassy_hal_internal::interrupt_mod!( SPU, CLOCK_POWER, - SPIM0_SPIS0_TWIM0_TWIS0_UARTE0, - SPIM1_SPIS1_TWIM1_TWIS1_UARTE1, - SPIM2_SPIS2_TWIM2_TWIS2_UARTE2, - SPIM3_SPIS3_TWIM3_TWIS3_UARTE3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, GPIOTE0, SAADC, TIMER0, @@ -419,8 +368,8 @@ embassy_hal_internal::interrupt_mod!( PWM0, PWM1, PWM2, - PDM, PWM3, + PDM, I2S, IPC, FPU, diff --git a/embassy-nrf/src/chips/nrf9160.rs b/embassy-nrf/src/chips/nrf9160.rs index 8107ca175..5d04a72e5 100644 --- a/embassy-nrf/src/chips/nrf9160.rs +++ b/embassy-nrf/src/chips/nrf9160.rs @@ -2,177 +2,124 @@ #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { - // The nRF9160 has a secure and non-secure (NS) mode. - // To avoid cfg spam, we remove _ns or _s suffixes here. - - pub use nrf9160_pac::NVIC_PRIO_BITS; - - #[cfg(feature="rt")] - #[doc(no_inline)] - pub use nrf9160_pac::interrupt; - - #[doc(no_inline)] - pub use nrf9160_pac::{ - Interrupt, - - cc_host_rgf_s as cc_host_rgf, - clock_ns as clock, - cryptocell_s as cryptocell, - ctrl_ap_peri_s as ctrl_ap_peri, - dppic_ns as dppic, - egu0_ns as egu0, - ficr_s as ficr, - fpu_ns as fpu, - gpiote0_s as gpiote, - i2s_ns as i2s, - ipc_ns as ipc, - kmu_ns as kmu, - nvmc_ns as nvmc, - p0_ns as p0, - pdm_ns as pdm, - power_ns as power, - pwm0_ns as pwm0, - regulators_ns as regulators, - rtc0_ns as rtc0, - saadc_ns as saadc, - spim0_ns as spim0, - spis0_ns as spis0, - spu_s as spu, - tad_s as tad, - timer0_ns as timer0, - twim0_ns as twim0, - twis0_ns as twis0, - uarte0_ns as uarte0, - uicr_s as uicr, - vmc_ns as vmc, - wdt_ns as wdt, - }; - - /// Non-Secure mode (NS) peripherals - pub mod ns { - #[doc(no_inline)] - pub use nrf9160_pac::{ - CLOCK_NS as CLOCK, - DPPIC_NS as DPPIC, - EGU0_NS as EGU0, - EGU1_NS as EGU1, - EGU2_NS as EGU2, - EGU3_NS as EGU3, - EGU4_NS as EGU4, - EGU5_NS as EGU5, - FPU_NS as FPU, - GPIOTE1_NS as GPIOTE1, - I2S_NS as I2S, - IPC_NS as IPC, - KMU_NS as KMU, - NVMC_NS as NVMC, - P0_NS as P0, - PDM_NS as PDM, - POWER_NS as POWER, - PWM0_NS as PWM0, - PWM1_NS as PWM1, - PWM2_NS as PWM2, - PWM3_NS as PWM3, - REGULATORS_NS as REGULATORS, - RTC0_NS as RTC0, - RTC1_NS as RTC1, - SAADC_NS as SAADC, - SPIM0_NS as SPIM0, - SPIM1_NS as SPIM1, - SPIM2_NS as SPIM2, - SPIM3_NS as SPIM3, - SPIS0_NS as SPIS0, - SPIS1_NS as SPIS1, - SPIS2_NS as SPIS2, - SPIS3_NS as SPIS3, - TIMER0_NS as TIMER0, - TIMER1_NS as TIMER1, - TIMER2_NS as TIMER2, - TWIM0_NS as TWIM0, - TWIM1_NS as TWIM1, - TWIM2_NS as TWIM2, - TWIM3_NS as TWIM3, - TWIS0_NS as TWIS0, - TWIS1_NS as TWIS1, - TWIS2_NS as TWIS2, - TWIS3_NS as TWIS3, - UARTE0_NS as UARTE0, - UARTE1_NS as UARTE1, - UARTE2_NS as UARTE2, - UARTE3_NS as UARTE3, - VMC_NS as VMC, - WDT_NS as WDT, - }; - } - - /// Secure mode (S) peripherals - pub mod s { - #[doc(no_inline)] - pub use nrf9160_pac::{ - CC_HOST_RGF_S as CC_HOST_RGF, - CLOCK_S as CLOCK, - CRYPTOCELL_S as CRYPTOCELL, - CTRL_AP_PERI_S as CTRL_AP_PERI, - DPPIC_S as DPPIC, - EGU0_S as EGU0, - EGU1_S as EGU1, - EGU2_S as EGU2, - EGU3_S as EGU3, - EGU4_S as EGU4, - EGU5_S as EGU5, - FICR_S as FICR, - FPU_S as FPU, - GPIOTE0_S as GPIOTE0, - I2S_S as I2S, - IPC_S as IPC, - KMU_S as KMU, - NVMC_S as NVMC, - P0_S as P0, - PDM_S as PDM, - POWER_S as POWER, - PWM0_S as PWM0, - PWM1_S as PWM1, - PWM2_S as PWM2, - PWM3_S as PWM3, - REGULATORS_S as REGULATORS, - RTC0_S as RTC0, - RTC1_S as RTC1, - SAADC_S as SAADC, - SPIM0_S as SPIM0, - SPIM1_S as SPIM1, - SPIM2_S as SPIM2, - SPIM3_S as SPIM3, - SPIS0_S as SPIS0, - SPIS1_S as SPIS1, - SPIS2_S as SPIS2, - SPIS3_S as SPIS3, - SPU_S as SPU, - TAD_S as TAD, - TIMER0_S as TIMER0, - TIMER1_S as TIMER1, - TIMER2_S as TIMER2, - TWIM0_S as TWIM0, - TWIM1_S as TWIM1, - TWIM2_S as TWIM2, - TWIM3_S as TWIM3, - TWIS0_S as TWIS0, - TWIS1_S as TWIS1, - TWIS2_S as TWIS2, - TWIS3_S as TWIS3, - UARTE0_S as UARTE0, - UARTE1_S as UARTE1, - UARTE2_S as UARTE2, - UARTE3_S as UARTE3, - UICR_S as UICR, - VMC_S as VMC, - WDT_S as WDT, - }; - } + pub use nrf_pac::*; #[cfg(feature = "_ns")] - pub use ns::*; + #[doc(no_inline)] + pub use nrf_pac::{ + CLOCK_NS as CLOCK, + DPPIC_NS as DPPIC, + EGU0_NS as EGU0, + EGU1_NS as EGU1, + EGU2_NS as EGU2, + EGU3_NS as EGU3, + EGU4_NS as EGU4, + EGU5_NS as EGU5, + FPU_NS as FPU, + GPIOTE1_NS as GPIOTE1, + I2S_NS as I2S, + IPC_NS as IPC, + KMU_NS as KMU, + NVMC_NS as NVMC, + P0_NS as P0, + PDM_NS as PDM, + POWER_NS as POWER, + PWM0_NS as PWM0, + PWM1_NS as PWM1, + PWM2_NS as PWM2, + PWM3_NS as PWM3, + REGULATORS_NS as REGULATORS, + RTC0_NS as RTC0, + RTC1_NS as RTC1, + SAADC_NS as SAADC, + SPIM0_NS as SPIM0, + SPIM1_NS as SPIM1, + SPIM2_NS as SPIM2, + SPIM3_NS as SPIM3, + SPIS0_NS as SPIS0, + SPIS1_NS as SPIS1, + SPIS2_NS as SPIS2, + SPIS3_NS as SPIS3, + TIMER0_NS as TIMER0, + TIMER1_NS as TIMER1, + TIMER2_NS as TIMER2, + TWIM0_NS as TWIM0, + TWIM1_NS as TWIM1, + TWIM2_NS as TWIM2, + TWIM3_NS as TWIM3, + TWIS0_NS as TWIS0, + TWIS1_NS as TWIS1, + TWIS2_NS as TWIS2, + TWIS3_NS as TWIS3, + UARTE0_NS as UARTE0, + UARTE1_NS as UARTE1, + UARTE2_NS as UARTE2, + UARTE3_NS as UARTE3, + VMC_NS as VMC, + WDT_NS as WDT, + }; + #[cfg(feature = "_s")] - pub use s::*; + #[doc(no_inline)] + pub use nrf_pac::{ + CC_HOST_RGF_S as CC_HOST_RGF, + CLOCK_S as CLOCK, + CRYPTOCELL_S as CRYPTOCELL, + CTRL_AP_PERI_S as CTRL_AP_PERI, + DPPIC_S as DPPIC, + EGU0_S as EGU0, + EGU1_S as EGU1, + EGU2_S as EGU2, + EGU3_S as EGU3, + EGU4_S as EGU4, + EGU5_S as EGU5, + FICR_S as FICR, + FPU_S as FPU, + GPIOTE0_S as GPIOTE0, + I2S_S as I2S, + IPC_S as IPC, + KMU_S as KMU, + NVMC_S as NVMC, + P0_S as P0, + PDM_S as PDM, + POWER_S as POWER, + PWM0_S as PWM0, + PWM1_S as PWM1, + PWM2_S as PWM2, + PWM3_S as PWM3, + REGULATORS_S as REGULATORS, + RTC0_S as RTC0, + RTC1_S as RTC1, + SAADC_S as SAADC, + SPIM0_S as SPIM0, + SPIM1_S as SPIM1, + SPIM2_S as SPIM2, + SPIM3_S as SPIM3, + SPIS0_S as SPIS0, + SPIS1_S as SPIS1, + SPIS2_S as SPIS2, + SPIS3_S as SPIS3, + SPU_S as SPU, + TAD_S as TAD, + TIMER0_S as TIMER0, + TIMER1_S as TIMER1, + TIMER2_S as TIMER2, + TWIM0_S as TWIM0, + TWIM1_S as TWIM1, + TWIM2_S as TWIM2, + TWIM3_S as TWIM3, + TWIS0_S as TWIS0, + TWIS1_S as TWIS1, + TWIS2_S as TWIS2, + TWIS3_S as TWIS3, + UARTE0_S as UARTE0, + UARTE1_S as UARTE1, + UARTE2_S as UARTE2, + UARTE3_S as UARTE3, + UICR_S as UICR, + VMC_S as VMC, + WDT_S as WDT, + }; } /// The maximum buffer size that the EasyDMA can send/recv in one operation. @@ -293,30 +240,30 @@ embassy_hal_internal::peripherals! { EGU5, } -impl_uarte!(SERIAL0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_uarte!(SERIAL1, UARTE1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_uarte!(SERIAL2, UARTE2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_uarte!(SERIAL3, UARTE3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); -impl_spim!(SERIAL0, SPIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_spim!(SERIAL1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_spim!(SERIAL2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_spim!(SERIAL3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); -impl_spis!(SERIAL0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_spis!(SERIAL1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_spis!(SERIAL2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_spis!(SERIAL3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); -impl_twim!(SERIAL0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_twim!(SERIAL1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_twim!(SERIAL2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_twim!(SERIAL3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); -impl_twis!(SERIAL0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_twis!(SERIAL1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_twis!(SERIAL2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_twis!(SERIAL3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); @@ -395,13 +342,15 @@ impl_egu!(EGU3, EGU3, EGU3); impl_egu!(EGU4, EGU4, EGU4); impl_egu!(EGU5, EGU5, EGU5); +impl_wdt!(WDT, WDT, WDT, 0); + embassy_hal_internal::interrupt_mod!( SPU, CLOCK_POWER, - UARTE0_SPIM0_SPIS0_TWIM0_TWIS0, - UARTE1_SPIM1_SPIS1_TWIM1_TWIS1, - UARTE2_SPIM2_SPIS2_TWIM2_TWIS2, - UARTE3_SPIM3_SPIS3_TWIM3_TWIS3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, GPIOTE0, SAADC, TIMER0, @@ -419,8 +368,8 @@ embassy_hal_internal::interrupt_mod!( PWM0, PWM1, PWM2, - PDM, PWM3, + PDM, I2S, IPC, FPU, diff --git a/embassy-nrf/src/egu.rs b/embassy-nrf/src/egu.rs index 204446d29..028396c7c 100644 --- a/embassy-nrf/src/egu.rs +++ b/embassy-nrf/src/egu.rs @@ -7,20 +7,19 @@ use core::marker::PhantomData; -use embassy_hal_internal::into_ref; +use embassy_hal_internal::PeripheralType; use crate::ppi::{Event, Task}; -use crate::{interrupt, pac, Peripheral, PeripheralRef}; +use crate::{interrupt, pac, Peri}; /// An instance of the EGU. pub struct Egu<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } impl<'d, T: Instance> Egu<'d, T> { /// Create a new EGU instance. - pub fn new(_p: impl Peripheral

+ 'd) -> Self { - into_ref!(_p); + pub fn new(_p: Peri<'d, T>) -> Self { Self { _p } } @@ -34,12 +33,12 @@ impl<'d, T: Instance> Egu<'d, T> { } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::egu0::RegisterBlock; + fn regs() -> pac::egu::Egu; } /// Basic Egu instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -47,8 +46,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_egu { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::egu::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::egu0::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::egu::Egu { + pac::$pac_type } } impl crate::egu::Instance for peripherals::$type { @@ -68,32 +67,26 @@ impl<'d, T: Instance> Trigger<'d, T> { pub fn task(&self) -> Task<'d> { let nr = self.number as usize; let regs = T::regs(); - Task::from_reg(®s.tasks_trigger[nr]) + Task::from_reg(regs.tasks_trigger(nr)) } /// Get event for this trigger to use with PPI. pub fn event(&self) -> Event<'d> { let nr = self.number as usize; let regs = T::regs(); - Event::from_reg(®s.events_triggered[nr]) + Event::from_reg(regs.events_triggered(nr)) } /// Enable interrupts for this trigger pub fn enable_interrupt(&mut self) { let regs = T::regs(); - unsafe { - regs.intenset - .modify(|r, w| w.bits(r.bits() | (1 << self.number as usize))) - }; + regs.intenset().modify(|w| w.set_triggered(self.number as usize, true)); } /// Enable interrupts for this trigger pub fn disable_interrupt(&mut self) { let regs = T::regs(); - unsafe { - regs.intenclr - .modify(|r, w| w.bits(r.bits() | (1 << self.number as usize))) - }; + regs.intenset().modify(|w| w.set_triggered(self.number as usize, false)); } } diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index dbc26ea3f..d02da9ac5 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -5,17 +5,14 @@ use core::convert::Infallible; use core::hint::unreachable_unchecked; use cfg_if::cfg_if; -use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; -#[cfg(feature = "nrf51")] +use crate::pac; +use crate::pac::common::{Reg, RW}; 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}; +use crate::pac::gpio::vals; +#[cfg(not(feature = "_nrf51"))] +use crate::pac::shared::{regs::Psel, vals::Connect}; /// A GPIO port with up to 32 pins. #[derive(Debug, Eq, PartialEq)] @@ -26,10 +23,14 @@ pub enum Port { /// Port 1, only available on some MCUs. #[cfg(feature = "_gpio-p1")] Port1, + + /// Port 2, only available on some MCUs. + #[cfg(feature = "_gpio-p2")] + Port2, } /// Pull setting for an input. -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Pull { /// No pull. @@ -48,7 +49,7 @@ pub struct Input<'d> { impl<'d> Input<'d> { /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. #[inline] - pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(pull); @@ -102,8 +103,83 @@ impl From for bool { } } +/// Drive strength settings for a given output level. +// These numbers match vals::Drive exactly so hopefully the compiler will unify them. +#[cfg(feature = "_nrf54l")] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum LevelDrive { + /// Disconnect (do not drive the output at all) + Disconnect = 2, + /// Standard + Standard = 0, + /// High drive + High = 1, + /// Extra high drive + ExtraHigh = 3, +} + /// Drive strength settings for an output pin. -// These numbers match DRIVE_A exactly so hopefully the compiler will unify them. +/// +/// This is a combination of two drive levels, used when the pin is set +/// low and high respectively. +#[cfg(feature = "_nrf54l")] +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OutputDrive { + low: LevelDrive, + high: LevelDrive, +} + +#[cfg(feature = "_nrf54l")] +#[allow(non_upper_case_globals)] +impl OutputDrive { + /// Standard '0', standard '1' + pub const Standard: Self = Self { + low: LevelDrive::Standard, + high: LevelDrive::Standard, + }; + /// High drive '0', standard '1' + pub const HighDrive0Standard1: Self = Self { + low: LevelDrive::High, + high: LevelDrive::Standard, + }; + /// Standard '0', high drive '1' + pub const Standard0HighDrive1: Self = Self { + low: LevelDrive::Standard, + high: LevelDrive::High, + }; + /// High drive '0', high 'drive '1' + pub const HighDrive: Self = Self { + low: LevelDrive::High, + high: LevelDrive::High, + }; + /// Disconnect '0' standard '1' (normally used for wired-or connections) + pub const Disconnect0Standard1: Self = Self { + low: LevelDrive::Disconnect, + high: LevelDrive::Standard, + }; + /// Disconnect '0', high drive '1' (normally used for wired-or connections) + pub const Disconnect0HighDrive1: Self = Self { + low: LevelDrive::Disconnect, + high: LevelDrive::High, + }; + /// Standard '0'. disconnect '1' (also known as "open drain", normally used for wired-and connections) + pub const Standard0Disconnect1: Self = Self { + low: LevelDrive::Standard, + high: LevelDrive::Disconnect, + }; + /// High drive '0', disconnect '1' (also known as "open drain", normally used for wired-and connections) + pub const HighDrive0Disconnect1: Self = Self { + low: LevelDrive::High, + high: LevelDrive::Disconnect, + }; +} + +/// Drive strength settings for an output pin. +// These numbers match vals::Drive exactly so hopefully the compiler will unify them. +#[cfg(not(feature = "_nrf54l"))] #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] @@ -134,7 +210,7 @@ pub struct Output<'d> { 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

+ 'd, initial_output: Level, drive: OutputDrive) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level, drive: OutputDrive) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -188,24 +264,43 @@ impl<'d> Output<'d> { } } -pub(crate) fn convert_drive(drive: OutputDrive) -> DRIVE_A { - match drive { - OutputDrive::Standard => DRIVE_A::S0S1, - OutputDrive::HighDrive0Standard1 => DRIVE_A::H0S1, - OutputDrive::Standard0HighDrive1 => DRIVE_A::S0H1, - OutputDrive::HighDrive => DRIVE_A::H0H1, - OutputDrive::Disconnect0Standard1 => DRIVE_A::D0S1, - OutputDrive::Disconnect0HighDrive1 => DRIVE_A::D0H1, - OutputDrive::Standard0Disconnect1 => DRIVE_A::S0D1, - OutputDrive::HighDrive0Disconnect1 => DRIVE_A::H0D1, +pub(crate) fn convert_drive(w: &mut pac::gpio::regs::PinCnf, drive: OutputDrive) { + #[cfg(not(feature = "_nrf54l"))] + { + let drive = match drive { + OutputDrive::Standard => vals::Drive::S0S1, + OutputDrive::HighDrive0Standard1 => vals::Drive::H0S1, + OutputDrive::Standard0HighDrive1 => vals::Drive::S0H1, + OutputDrive::HighDrive => vals::Drive::H0H1, + OutputDrive::Disconnect0Standard1 => vals::Drive::D0S1, + OutputDrive::Disconnect0HighDrive1 => vals::Drive::D0H1, + OutputDrive::Standard0Disconnect1 => vals::Drive::S0D1, + OutputDrive::HighDrive0Disconnect1 => vals::Drive::H0D1, + }; + w.set_drive(drive); + } + + #[cfg(feature = "_nrf54l")] + { + fn convert(d: LevelDrive) -> vals::Drive { + match d { + LevelDrive::Disconnect => vals::Drive::D, + LevelDrive::Standard => vals::Drive::S, + LevelDrive::High => vals::Drive::H, + LevelDrive::ExtraHigh => vals::Drive::E, + } + } + + w.set_drive0(convert(drive.low)); + w.set_drive0(convert(drive.high)); } } -fn convert_pull(pull: Pull) -> PULL_A { +fn convert_pull(pull: Pull) -> vals::Pull { match pull { - Pull::None => PULL_A::DISABLED, - Pull::Up => PULL_A::PULLUP, - Pull::Down => PULL_A::PULLDOWN, + Pull::None => vals::Pull::DISABLED, + Pull::Up => vals::Pull::PULLUP, + Pull::Down => vals::Pull::PULLDOWN, } } @@ -215,7 +310,7 @@ fn convert_pull(pull: Pull) -> PULL_A { /// 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> { - pub(crate) pin: PeripheralRef<'d, AnyPin>, + pub(crate) pin: Peri<'d, AnyPin>, } impl<'d> Flex<'d> { @@ -224,22 +319,20 @@ impl<'d> Flex<'d> { /// 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

+ 'd) -> Self { - into_ref!(pin); + pub fn new(pin: Peri<'d, impl Pin>) -> Self { // Pin will be in disconnected state. - Self { pin: pin.map_into() } + Self { pin: pin.into() } } /// Put the pin into input mode. #[inline] pub fn set_as_input(&mut self, pull: Pull) { self.pin.conf().write(|w| { - w.dir().input(); - w.input().connect(); - w.pull().variant(convert_pull(pull)); - w.drive().s0s1(); - w.sense().disabled(); - w + w.set_dir(vals::Dir::INPUT); + w.set_input(vals::Input::CONNECT); + w.set_pull(convert_pull(pull)); + convert_drive(w, OutputDrive::Standard); + w.set_sense(vals::Sense::DISABLED); }); } @@ -250,12 +343,11 @@ impl<'d> Flex<'d> { #[inline] pub fn set_as_output(&mut self, drive: OutputDrive) { self.pin.conf().write(|w| { - w.dir().output(); - w.input().disconnect(); - w.pull().disabled(); - w.drive().variant(convert_drive(drive)); - w.sense().disabled(); - w + w.set_dir(vals::Dir::OUTPUT); + w.set_input(vals::Input::DISCONNECT); + w.set_pull(vals::Pull::DISABLED); + convert_drive(w, drive); + w.set_sense(vals::Sense::DISABLED); }); } @@ -271,31 +363,32 @@ impl<'d> Flex<'d> { #[inline] pub fn set_as_input_output(&mut self, pull: Pull, drive: OutputDrive) { self.pin.conf().write(|w| { - w.dir().output(); - w.input().connect(); - w.pull().variant(convert_pull(pull)); - w.drive().variant(convert_drive(drive)); - w.sense().disabled(); - w + w.set_dir(vals::Dir::OUTPUT); + w.set_input(vals::Input::CONNECT); + w.set_pull(convert_pull(pull)); + convert_drive(w, drive); + w.set_sense(vals::Sense::DISABLED); }); } /// Put the pin into disconnected mode. #[inline] pub fn set_as_disconnected(&mut self) { - self.pin.conf().reset(); + self.pin.conf().write(|w| { + w.set_input(vals::Input::DISCONNECT); + }); } /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { - !self.is_low() + self.pin.block().in_().read().pin(self.pin.pin() as _) } /// Get whether the pin input level is low. #[inline] pub fn is_low(&self) -> bool { - self.pin.block().in_.read().bits() & (1 << self.pin.pin()) == 0 + !self.is_high() } /// Get the pin input level. @@ -338,13 +431,13 @@ impl<'d> Flex<'d> { /// Get whether the output level is set to high. #[inline] pub fn is_set_high(&self) -> bool { - !self.is_set_low() + self.pin.block().out().read().pin(self.pin.pin() as _) } /// Get whether the output level is set to low. #[inline] pub fn is_set_low(&self) -> bool { - self.pin.block().out.read().bits() & (1 << self.pin.pin()) == 0 + !self.is_set_high() } /// Get the current output level. @@ -356,7 +449,7 @@ impl<'d> Flex<'d> { impl<'d> Drop for Flex<'d> { fn drop(&mut self) { - self.pin.conf().reset(); + self.set_as_disconnected(); } } @@ -375,41 +468,41 @@ pub(crate) trait SealedPin { } #[inline] - 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(), - _ => unreachable_unchecked(), - } + fn block(&self) -> gpio::Gpio { + match self.pin_port() / 32 { + #[cfg(feature = "_nrf51")] + 0 => pac::GPIO, + #[cfg(not(feature = "_nrf51"))] + 0 => pac::P0, + #[cfg(feature = "_gpio-p1")] + 1 => pac::P1, + #[cfg(feature = "_gpio-p2")] + 2 => pac::P2, + _ => unsafe { unreachable_unchecked() }, } } #[inline] - fn conf(&self) -> &gpio::PIN_CNF { - &self.block().pin_cnf[self._pin() as usize] + fn conf(&self) -> Reg { + self.block().pin_cnf(self._pin() as usize) } /// Set the output as high. #[inline] fn set_high(&self) { - unsafe { self.block().outset.write(|w| w.bits(1u32 << self._pin())) } + self.block().outset().write(|w| w.set_pin(self._pin() as _, true)) } /// Set the output as low. #[inline] fn set_low(&self) { - unsafe { self.block().outclr.write(|w| w.bits(1u32 << self._pin())) } + self.block().outclr().write(|w| w.set_pin(self._pin() as _, true)) } } /// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an [AnyPin]. #[allow(private_bounds)] -pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { /// Number of the pin within the port (0..31) #[inline] fn pin(&self) -> u8 { @@ -423,28 +516,23 @@ pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static 0 => Port::Port0, #[cfg(feature = "_gpio-p1")] 1 => Port::Port1, + #[cfg(feature = "_gpio-p2")] + 2 => Port::Port2, _ => unsafe { unreachable_unchecked() }, } } /// Peripheral port register value #[inline] - fn psel_bits(&self) -> u32 { - self.pin_port() as u32 - } - - /// Convert from concrete pin type PX_XX to type erased `AnyPin`. - #[inline] - fn degrade(self) -> AnyPin { - AnyPin { - pin_port: self.pin_port(), - } + #[cfg(not(feature = "_nrf51"))] + fn psel_bits(&self) -> pac::shared::regs::Psel { + pac::shared::regs::Psel(self.pin_port() as u32) } } /// Type-erased GPIO pin pub struct AnyPin { - pin_port: u8, + pub(crate) pin_port: u8, } impl AnyPin { @@ -453,8 +541,8 @@ impl AnyPin { /// # Safety /// - `pin_port` should not in use by another driver. #[inline] - pub unsafe fn steal(pin_port: u8) -> Self { - Self { pin_port } + pub unsafe fn steal(pin_port: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_port }) } } @@ -470,27 +558,35 @@ impl SealedPin for AnyPin { // ==================== #[cfg(not(feature = "_nrf51"))] +#[cfg_attr(feature = "_nrf54l", allow(unused))] // TODO pub(crate) trait PselBits { - fn psel_bits(&self) -> u32; + fn psel_bits(&self) -> pac::shared::regs::Psel; } #[cfg(not(feature = "_nrf51"))] -impl<'a, P: Pin> PselBits for Option> { +impl<'a, P: Pin> PselBits for Option> { #[inline] - fn psel_bits(&self) -> u32 { + fn psel_bits(&self) -> pac::shared::regs::Psel { match self { Some(pin) => pin.psel_bits(), - None => 1u32 << 31, + None => DISCONNECTED, } } } +#[cfg(not(feature = "_nrf51"))] +#[cfg_attr(feature = "_nrf54l", allow(unused))] // TODO +pub(crate) const DISCONNECTED: Psel = Psel(1 << 31); + +#[cfg(not(feature = "_nrf51"))] #[allow(dead_code)] -pub(crate) fn deconfigure_pin(psel_bits: u32) { - if psel_bits & 0x8000_0000 != 0 { +pub(crate) fn deconfigure_pin(psel: Psel) { + if psel.connect() == Connect::DISCONNECTED { return; } - unsafe { AnyPin::steal(psel_bits as _).conf().reset() } + unsafe { AnyPin::steal(psel.0 as _) }.conf().write(|w| { + w.set_input(vals::Input::DISCONNECT); + }) } // ==================== @@ -506,8 +602,10 @@ macro_rules! impl_pin { } impl From for crate::gpio::AnyPin { - fn from(val: peripherals::$type) -> Self { - crate::gpio::Pin::degrade(val) + fn from(_val: peripherals::$type) -> Self { + Self { + pin_port: $port_num * 32 + $pin_num, + } } } }; diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index 9d97c7be9..d169b49f9 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -4,15 +4,19 @@ use core::convert::Infallible; use core::future::{poll_fn, Future}; use core::task::{Context, Poll}; -use embassy_hal_internal::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use crate::gpio::{AnyPin, Flex, Input, Output, Pin as GpioPin, SealedPin as _}; use crate::interrupt::InterruptExt; +#[cfg(not(feature = "_nrf51"))] +use crate::pac::gpio::vals::Detectmode; +use crate::pac::gpio::vals::Sense; +use crate::pac::gpiote::vals::{Mode, Outinit, Polarity}; use crate::ppi::{Event, Task}; use crate::{interrupt, pac, peripherals}; -#[cfg(feature = "nrf51")] +#[cfg(feature = "_nrf51")] /// Amount of GPIOTE channels in the chip. const CHANNEL_COUNT: usize = 4; #[cfg(not(feature = "_nrf51"))] @@ -25,9 +29,8 @@ const PIN_COUNT: usize = 48; const PIN_COUNT: usize = 32; #[allow(clippy::declare_interior_mutable_const)] -const NEW_AW: AtomicWaker = AtomicWaker::new(); -static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; -static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; +static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [const { AtomicWaker::new() }; PIN_COUNT]; /// Polarity for listening to events for GPIOTE input channels. pub enum InputChannelPolarity { @@ -51,14 +54,14 @@ pub enum OutputChannelPolarity { Toggle, } -fn regs() -> &'static pac::gpiote::RegisterBlock { +fn regs() -> pac::gpiote::Gpiote { cfg_if::cfg_if! { if #[cfg(any(feature="nrf5340-app-s", feature="nrf9160-s", feature="nrf9120-s"))] { - unsafe { &*pac::GPIOTE0::ptr() } + pac::GPIOTE0 } else if #[cfg(any(feature="nrf5340-app-ns", feature="nrf9160-ns", feature="nrf9120-ns"))] { - unsafe { &*pac::GPIOTE1::ptr() } + pac::GPIOTE1 } else { - unsafe { &*pac::GPIOTE::ptr() } + pac::GPIOTE } } } @@ -68,15 +71,15 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) { #[cfg(not(feature = "_nrf51"))] { #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] - let ports = unsafe { &[&*pac::P0::ptr(), &*pac::P1::ptr()] }; + let ports = &[pac::P0, pac::P1]; #[cfg(not(any(feature = "_nrf51", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340")))] - let ports = unsafe { &[&*pac::P0::ptr()] }; + let ports = &[pac::P0]; for &p in ports { // Enable latched detection - p.detectmode.write(|w| w.detectmode().ldetect()); + p.detectmode().write(|w| w.set_detectmode(Detectmode::LDETECT)); // Clear latch - p.latch.write(|w| unsafe { w.bits(0xFFFFFFFF) }) + p.latch().write(|w| w.0 = 0xFFFFFFFF) } } @@ -93,7 +96,7 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) { unsafe { irq.enable() }; let g = regs(); - g.intenset.write(|w| w.port().set()); + g.intenset().write(|w| w.set_port(true)); } #[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s", feature = "nrf9120-s"))] @@ -121,47 +124,47 @@ unsafe fn handle_gpiote_interrupt() { let g = regs(); for i in 0..CHANNEL_COUNT { - if g.events_in[i].read().bits() != 0 { - g.intenclr.write(|w| w.bits(1 << i)); + if g.events_in(i).read() != 0 { + g.intenclr().write(|w| w.0 = 1 << i); CHANNEL_WAKERS[i].wake(); } } - if g.events_port.read().bits() != 0 { - g.events_port.write(|w| w); + if g.events_port().read() != 0 { + g.events_port().write_value(0); #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] - let ports = &[&*pac::P0::ptr(), &*pac::P1::ptr()]; + let ports = &[pac::P0, pac::P1]; #[cfg(not(any(feature = "_nrf51", feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340")))] - let ports = &[&*pac::P0::ptr()]; + let ports = &[pac::P0]; #[cfg(feature = "_nrf51")] - let ports = unsafe { &[&*pac::GPIO::ptr()] }; + let ports = &[pac::GPIO]; #[cfg(feature = "_nrf51")] for (port, &p) in ports.iter().enumerate() { - let inp = p.in_.read().bits(); + let inp = p.in_().read(); 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, + let fired = match p.pin_cnf(pin as usize).read().sense() { + Sense::HIGH => inp.pin(pin), + Sense::LOW => !inp.pin(pin), _ => false, }; if fired { PORT_WAKERS[port * 32 + pin as usize].wake(); - p.pin_cnf[pin as usize].modify(|_, w| w.sense().disabled()); + p.pin_cnf(pin as usize).modify(|w| w.set_sense(Sense::DISABLED)); } } } #[cfg(not(feature = "_nrf51"))] for (port, &p) in ports.iter().enumerate() { - let bits = p.latch.read().bits(); + let bits = p.latch().read().0; for pin in BitIter(bits) { - p.pin_cnf[pin as usize].modify(|_, w| w.sense().disabled()); + p.pin_cnf(pin as usize).modify(|w| w.set_sense(Sense::DISABLED)); PORT_WAKERS[port * 32 + pin as usize].wake(); } - p.latch.write(|w| w.bits(bits)); + p.latch().write(|w| w.0 = bits); } } } @@ -186,7 +189,7 @@ impl Iterator for BitIter { /// GPIOTE channel driver in input mode pub struct InputChannel<'d> { - ch: PeripheralRef<'d, AnyChannel>, + ch: Peri<'d, AnyChannel>, pin: Input<'d>, } @@ -194,37 +197,36 @@ impl<'d> Drop for InputChannel<'d> { fn drop(&mut self) { let g = regs(); let num = self.ch.number(); - g.config[num].write(|w| w.mode().disabled()); - g.intenclr.write(|w| unsafe { w.bits(1 << num) }); + g.config(num).write(|w| w.set_mode(Mode::DISABLED)); + g.intenclr().write(|w| w.0 = 1 << num); } } impl<'d> InputChannel<'d> { /// Create a new GPIOTE input channel driver. - pub fn new(ch: impl Peripheral

+ 'd, pin: Input<'d>, polarity: InputChannelPolarity) -> Self { - into_ref!(ch); - + pub fn new(ch: Peri<'d, impl Channel>, pin: Input<'d>, polarity: InputChannelPolarity) -> Self { let g = regs(); let num = ch.number(); - g.config[num].write(|w| { + g.config(num).write(|w| { + w.set_mode(Mode::EVENT); match polarity { - InputChannelPolarity::HiToLo => w.mode().event().polarity().hi_to_lo(), - InputChannelPolarity::LoToHi => w.mode().event().polarity().lo_to_hi(), - InputChannelPolarity::None => w.mode().event().polarity().none(), - InputChannelPolarity::Toggle => w.mode().event().polarity().toggle(), + InputChannelPolarity::HiToLo => w.set_polarity(Polarity::HI_TO_LO), + InputChannelPolarity::LoToHi => w.set_polarity(Polarity::LO_TO_HI), + InputChannelPolarity::None => w.set_polarity(Polarity::NONE), + InputChannelPolarity::Toggle => w.set_polarity(Polarity::TOGGLE), }; #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] - w.port().bit(match pin.pin.pin.port() { + w.set_port(match pin.pin.pin.port() { crate::gpio::Port::Port0 => false, crate::gpio::Port::Port1 => true, }); - unsafe { w.psel().bits(pin.pin.pin.pin()) } + w.set_psel(pin.pin.pin.pin()); }); - g.events_in[num].reset(); + g.events_in(num).write_value(0); - InputChannel { ch: ch.map_into(), pin } + InputChannel { ch: ch.into(), pin } } /// Asynchronously wait for an event in this channel. @@ -233,13 +235,13 @@ impl<'d> InputChannel<'d> { let num = self.ch.number(); // Enable interrupt - g.events_in[num].reset(); - g.intenset.write(|w| unsafe { w.bits(1 << num) }); + g.events_in(num).write_value(0); + g.intenset().write(|w| w.0 = 1 << num); poll_fn(|cx| { CHANNEL_WAKERS[num].register(cx.waker()); - if g.events_in[num].read().bits() != 0 { + if g.events_in(num).read() != 0 { Poll::Ready(()) } else { Poll::Pending @@ -251,13 +253,13 @@ impl<'d> InputChannel<'d> { /// Returns the IN event, for use with PPI. pub fn event_in(&self) -> Event<'d> { let g = regs(); - Event::from_reg(&g.events_in[self.ch.number()]) + Event::from_reg(g.events_in(self.ch.number())) } } /// GPIOTE channel driver in output mode pub struct OutputChannel<'d> { - ch: PeripheralRef<'d, AnyChannel>, + ch: Peri<'d, AnyChannel>, _pin: Output<'d>, } @@ -265,39 +267,38 @@ impl<'d> Drop for OutputChannel<'d> { fn drop(&mut self) { let g = regs(); let num = self.ch.number(); - g.config[num].write(|w| w.mode().disabled()); - g.intenclr.write(|w| unsafe { w.bits(1 << num) }); + g.config(num).write(|w| w.set_mode(Mode::DISABLED)); + g.intenclr().write(|w| w.0 = 1 << num); } } impl<'d> OutputChannel<'d> { /// Create a new GPIOTE output channel driver. - pub fn new(ch: impl Peripheral

+ 'd, pin: Output<'d>, polarity: OutputChannelPolarity) -> Self { - into_ref!(ch); + pub fn new(ch: Peri<'d, impl Channel>, pin: Output<'d>, polarity: OutputChannelPolarity) -> Self { let g = regs(); let num = ch.number(); - g.config[num].write(|w| { - w.mode().task(); + g.config(num).write(|w| { + w.set_mode(Mode::TASK); match pin.is_set_high() { - true => w.outinit().high(), - false => w.outinit().low(), + true => w.set_outinit(Outinit::HIGH), + false => w.set_outinit(Outinit::LOW), }; match polarity { - OutputChannelPolarity::Set => w.polarity().lo_to_hi(), - OutputChannelPolarity::Clear => w.polarity().hi_to_lo(), - OutputChannelPolarity::Toggle => w.polarity().toggle(), + OutputChannelPolarity::Set => w.set_polarity(Polarity::HI_TO_LO), + OutputChannelPolarity::Clear => w.set_polarity(Polarity::LO_TO_HI), + OutputChannelPolarity::Toggle => w.set_polarity(Polarity::TOGGLE), }; #[cfg(any(feature = "nrf52833", feature = "nrf52840", feature = "_nrf5340"))] - w.port().bit(match pin.pin.pin.port() { + w.set_port(match pin.pin.pin.port() { crate::gpio::Port::Port0 => false, crate::gpio::Port::Port1 => true, }); - unsafe { w.psel().bits(pin.pin.pin.pin()) } + w.set_psel(pin.pin.pin.pin()); }); OutputChannel { - ch: ch.map_into(), + ch: ch.into(), _pin: pin, } } @@ -305,41 +306,41 @@ impl<'d> OutputChannel<'d> { /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle). pub fn out(&self) { let g = regs(); - g.tasks_out[self.ch.number()].write(|w| unsafe { w.bits(1) }); + g.tasks_out(self.ch.number()).write_value(1); } /// Triggers the SET task (set associated pin high). - #[cfg(not(feature = "nrf51"))] + #[cfg(not(feature = "_nrf51"))] pub fn set(&self) { let g = regs(); - g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) }); + g.tasks_set(self.ch.number()).write_value(1); } /// Triggers the CLEAR task (set associated pin low). - #[cfg(not(feature = "nrf51"))] + #[cfg(not(feature = "_nrf51"))] pub fn clear(&self) { let g = regs(); - g.tasks_clr[self.ch.number()].write(|w| unsafe { w.bits(1) }); + g.tasks_clr(self.ch.number()).write_value(1); } /// Returns the OUT task, for use with PPI. pub fn task_out(&self) -> Task<'d> { let g = regs(); - Task::from_reg(&g.tasks_out[self.ch.number()]) + Task::from_reg(g.tasks_out(self.ch.number())) } /// Returns the CLR task, for use with PPI. - #[cfg(not(feature = "nrf51"))] + #[cfg(not(feature = "_nrf51"))] pub fn task_clr(&self) -> Task<'d> { let g = regs(); - Task::from_reg(&g.tasks_clr[self.ch.number()]) + Task::from_reg(g.tasks_clr(self.ch.number())) } /// Returns the SET task, for use with PPI. - #[cfg(not(feature = "nrf51"))] + #[cfg(not(feature = "_nrf51"))] pub fn task_set(&self) -> Task<'d> { let g = regs(); - Task::from_reg(&g.tasks_set[self.ch.number()]) + Task::from_reg(g.tasks_set(self.ch.number())) } } @@ -347,14 +348,12 @@ impl<'d> OutputChannel<'d> { #[must_use = "futures do nothing unless you `.await` or poll them"] pub(crate) struct PortInputFuture<'a> { - pin: PeripheralRef<'a, AnyPin>, + pin: Peri<'a, AnyPin>, } impl<'a> PortInputFuture<'a> { - fn new(pin: impl Peripheral

+ 'a) -> Self { - Self { - pin: pin.into_ref().map_into(), - } + fn new(pin: Peri<'a, impl GpioPin>) -> Self { + Self { pin: pin.into() } } } @@ -362,7 +361,7 @@ impl<'a> Unpin for PortInputFuture<'a> {} impl<'a> Drop for PortInputFuture<'a> { fn drop(&mut self) { - self.pin.conf().modify(|_, w| w.sense().disabled()); + self.pin.conf().modify(|w| w.set_sense(Sense::DISABLED)); } } @@ -372,7 +371,7 @@ impl<'a> Future for PortInputFuture<'a> { fn poll(self: core::pin::Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { PORT_WAKERS[self.pin.pin_port() as usize].register(cx.waker()); - if self.pin.conf().read().sense().is_disabled() { + if self.pin.conf().read().sense() == Sense::DISABLED { Poll::Ready(()) } else { Poll::Pending @@ -410,14 +409,14 @@ impl<'d> Input<'d> { 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()); - PortInputFuture::new(&mut self.pin).await + self.pin.conf().modify(|w| w.set_sense(Sense::HIGH)); + PortInputFuture::new(self.pin.reborrow()).await } /// Wait until the pin is low. If it is already low, return immediately. pub async fn wait_for_low(&mut self) { - self.pin.conf().modify(|_, w| w.sense().low()); - PortInputFuture::new(&mut self.pin).await + self.pin.conf().modify(|w| w.set_sense(Sense::LOW)); + PortInputFuture::new(self.pin.reborrow()).await } /// Wait for the pin to undergo a transition from low to high. @@ -435,11 +434,11 @@ impl<'d> Flex<'d> { /// Wait for the pin to undergo any transition, i.e low to high OR high to low. pub async fn wait_for_any_edge(&mut self) { if self.is_high() { - self.pin.conf().modify(|_, w| w.sense().low()); + self.pin.conf().modify(|w| w.set_sense(Sense::LOW)); } else { - self.pin.conf().modify(|_, w| w.sense().high()); + self.pin.conf().modify(|w| w.set_sense(Sense::HIGH)); } - PortInputFuture::new(&mut self.pin).await + PortInputFuture::new(self.pin.reborrow()).await } } @@ -451,24 +450,14 @@ trait SealedChannel {} /// /// Implemented by all GPIOTE channels. #[allow(private_bounds)] -pub trait Channel: SealedChannel + Into + Sized + 'static { +pub trait Channel: PeripheralType + SealedChannel + Into + Sized + 'static { /// Get the channel number. fn number(&self) -> usize; - - /// Convert this channel to a type-erased `AnyChannel`. - /// - /// This allows using several channels in situations that might require - /// them to be the same type, like putting them in an array. - fn degrade(self) -> AnyChannel { - AnyChannel { - number: self.number() as u8, - } - } } /// Type-erased channel. /// -/// Obtained by calling `Channel::degrade`. +/// Obtained by calling `Channel::into()`. /// /// This allows using several channels in situations that might require /// them to be the same type, like putting them in an array. @@ -494,7 +483,9 @@ macro_rules! impl_channel { impl From for AnyChannel { fn from(val: peripherals::$type) -> Self { - Channel::degrade(val) + Self { + number: val.number() as u8, + } } } }; @@ -504,13 +495,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"))] +#[cfg(not(feature = "_nrf51"))] impl_channel!(GPIOTE_CH4, 4); -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf51"))] impl_channel!(GPIOTE_CH5, 5); -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf51"))] impl_channel!(GPIOTE_CH6, 6); -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf51"))] impl_channel!(GPIOTE_CH7, 7); // ==================== diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs index 5f565a9b7..a7dde8cd7 100644 --- a/embassy-nrf/src/i2s.rs +++ b/embassy-nrf/src/i2s.rs @@ -10,14 +10,14 @@ use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; -use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::gpio::{AnyPin, Pin as GpioPin, PselBits}; use crate::interrupt::typelevel::Interrupt; -use crate::pac::i2s::RegisterBlock; +use crate::pac::i2s::vals; use crate::util::slice_in_ram_or; -use crate::{interrupt, Peripheral, EASY_DMA_SIZE}; +use crate::{interrupt, pac, EASY_DMA_SIZE}; /// Type alias for `MultiBuffering` with 2 buffers. pub type DoubleBuffering = MultiBuffering; @@ -117,9 +117,20 @@ pub enum MckFreq { } impl MckFreq { - const REGISTER_VALUES: &'static [u32] = &[ - 0x20000000, 0x18000000, 0x16000000, 0x11000000, 0x10000000, 0x0C000000, 0x0B000000, 0x08800000, 0x08400000, - 0x08000000, 0x06000000, 0x04100000, 0x020C0000, + const REGISTER_VALUES: &'static [vals::Mckfreq] = &[ + vals::Mckfreq::_32MDIV8, + vals::Mckfreq::_32MDIV10, + vals::Mckfreq::_32MDIV11, + vals::Mckfreq::_32MDIV15, + vals::Mckfreq::_32MDIV16, + vals::Mckfreq::_32MDIV21, + vals::Mckfreq::_32MDIV23, + vals::Mckfreq::_32MDIV30, + vals::Mckfreq::_32MDIV31, + vals::Mckfreq::_32MDIV32, + vals::Mckfreq::_32MDIV42, + vals::Mckfreq::_32MDIV63, + vals::Mckfreq::_32MDIV125, ]; const FREQUENCIES: &'static [u32] = &[ @@ -128,7 +139,7 @@ impl MckFreq { ]; /// Return the value that needs to be written to the register. - pub fn to_register_value(&self) -> u32 { + pub fn to_register_value(&self) -> vals::Mckfreq { Self::REGISTER_VALUES[usize::from(*self)] } @@ -174,8 +185,8 @@ impl Ratio { const RATIOS: &'static [u32] = &[32, 48, 64, 96, 128, 192, 256, 384, 512]; /// Return the value that needs to be written to the register. - pub fn to_register_value(&self) -> u8 { - usize::from(*self) as u8 + pub fn to_register_value(&self) -> vals::Ratio { + vals::Ratio::from_bits(*self as u8) } /// Return the divisor for this ratio @@ -304,9 +315,9 @@ pub enum SampleWidth { _24bit, } -impl From for u8 { +impl From for vals::Swidth { fn from(variant: SampleWidth) -> Self { - variant as _ + vals::Swidth::from_bits(variant as u8) } } @@ -319,11 +330,11 @@ pub enum Align { Right, } -impl From for bool { +impl From for vals::Align { fn from(variant: Align) -> Self { match variant { - Align::Left => false, - Align::Right => true, + Align::Left => vals::Align::LEFT, + Align::Right => vals::Align::RIGHT, } } } @@ -337,11 +348,11 @@ pub enum Format { Aligned, } -impl From for bool { +impl From for vals::Format { fn from(variant: Format) -> Self { match variant { - Format::I2S => false, - Format::Aligned => true, + Format::I2S => vals::Format::I2S, + Format::Aligned => vals::Format::ALIGNED, } } } @@ -357,9 +368,9 @@ pub enum Channels { MonoRight, } -impl From for u8 { +impl From for vals::Channels { fn from(variant: Channels) -> Self { - variant as _ + vals::Channels::from_bits(variant as u8) } } @@ -395,12 +406,12 @@ impl interrupt::typelevel::Handler for InterruptHandl /// I2S driver. pub struct I2S<'d, T: Instance> { - i2s: PeripheralRef<'d, T>, - mck: Option>, - sck: PeripheralRef<'d, AnyPin>, - lrck: PeripheralRef<'d, AnyPin>, - sdin: Option>, - sdout: Option>, + i2s: Peri<'d, T>, + mck: Option>, + sck: Peri<'d, AnyPin>, + lrck: Peri<'d, AnyPin>, + sdin: Option>, + sdout: Option>, master_clock: Option, config: Config, } @@ -408,20 +419,19 @@ pub struct I2S<'d, T: Instance> { impl<'d, T: Instance> I2S<'d, T> { /// Create a new I2S in master mode pub fn new_master( - i2s: impl Peripheral

+ 'd, + i2s: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - mck: impl Peripheral

+ 'd, - sck: impl Peripheral

+ 'd, - lrck: impl Peripheral

+ 'd, + mck: Peri<'d, impl GpioPin>, + sck: Peri<'d, impl GpioPin>, + lrck: Peri<'d, impl GpioPin>, master_clock: MasterClock, config: Config, ) -> Self { - into_ref!(i2s, mck, sck, lrck); Self { i2s, - mck: Some(mck.map_into()), - sck: sck.map_into(), - lrck: lrck.map_into(), + mck: Some(mck.into()), + sck: sck.into(), + lrck: lrck.into(), sdin: None, sdout: None, master_clock: Some(master_clock), @@ -431,18 +441,17 @@ impl<'d, T: Instance> I2S<'d, T> { /// Create a new I2S in slave mode pub fn new_slave( - i2s: impl Peripheral

+ 'd, + i2s: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - sck: impl Peripheral

+ 'd, - lrck: impl Peripheral

+ 'd, + sck: Peri<'d, impl GpioPin>, + lrck: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(i2s, sck, lrck); Self { i2s, mck: None, - sck: sck.map_into(), - lrck: lrck.map_into(), + sck: sck.into(), + lrck: lrck.into(), sdin: None, sdout: None, master_clock: None, @@ -453,10 +462,10 @@ impl<'d, T: Instance> I2S<'d, T> { /// I2S output only pub fn output( mut self, - sdout: impl Peripheral

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

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

+ 'd, - sdout: impl Peripheral

+ 'd, + sdin: Peri<'d, impl GpioPin>, + sdout: Peri<'d, impl GpioPin>, buffers_out: MultiBuffering, buffers_in: MultiBuffering, ) -> FullDuplexStream<'d, T, S, NB, NS> { - self.sdout = Some(sdout.into_ref().map_into()); - self.sdin = Some(sdin.into_ref().map_into()); + self.sdout = Some(sdout.into()); + self.sdin = Some(sdin.into()); FullDuplexStream { _p: self.build(), @@ -494,7 +503,7 @@ impl<'d, T: Instance> I2S<'d, T> { } } - fn build(self) -> PeripheralRef<'d, T> { + fn build(self) -> Peri<'d, T> { self.apply_config(); self.select_pins(); self.setup_interrupt(); @@ -506,61 +515,32 @@ impl<'d, T: Instance> I2S<'d, T> { } fn apply_config(&self) { - let c = &T::regs().config; + let c = T::regs().config(); match &self.master_clock { Some(MasterClock { freq, ratio }) => { - c.mode.write(|w| w.mode().master()); - c.mcken.write(|w| w.mcken().enabled()); - c.mckfreq - .write(|w| unsafe { w.mckfreq().bits(freq.to_register_value()) }); - c.ratio.write(|w| unsafe { w.ratio().bits(ratio.to_register_value()) }); + c.mode().write(|w| w.set_mode(vals::Mode::MASTER)); + c.mcken().write(|w| w.set_mcken(true)); + c.mckfreq().write(|w| w.set_mckfreq(freq.to_register_value())); + c.ratio().write(|w| w.set_ratio(ratio.to_register_value())); } None => { - c.mode.write(|w| w.mode().slave()); + c.mode().write(|w| w.set_mode(vals::Mode::SLAVE)); } }; - c.swidth - .write(|w| unsafe { w.swidth().bits(self.config.sample_width.into()) }); - c.align.write(|w| w.align().bit(self.config.align.into())); - c.format.write(|w| w.format().bit(self.config.format.into())); - c.channels - .write(|w| unsafe { w.channels().bits(self.config.channels.into()) }); + c.swidth().write(|w| w.set_swidth(self.config.sample_width.into())); + c.align().write(|w| w.set_align(self.config.align.into())); + c.format().write(|w| w.set_format(self.config.format.into())); + c.channels().write(|w| w.set_channels(self.config.channels.into())); } fn select_pins(&self) { - let psel = &T::regs().psel; - - if let Some(mck) = &self.mck { - psel.mck.write(|w| { - unsafe { w.bits(mck.psel_bits()) }; - w.connect().connected() - }); - } - - psel.sck.write(|w| { - unsafe { w.bits(self.sck.psel_bits()) }; - w.connect().connected() - }); - - psel.lrck.write(|w| { - unsafe { w.bits(self.lrck.psel_bits()) }; - w.connect().connected() - }); - - if let Some(sdin) = &self.sdin { - psel.sdin.write(|w| { - unsafe { w.bits(sdin.psel_bits()) }; - w.connect().connected() - }); - } - - if let Some(sdout) = &self.sdout { - psel.sdout.write(|w| { - unsafe { w.bits(sdout.psel_bits()) }; - w.connect().connected() - }); - } + let psel = T::regs().psel(); + psel.mck().write_value(self.mck.psel_bits()); + psel.sck().write_value(self.sck.psel_bits()); + psel.lrck().write_value(self.lrck.psel_bits()); + psel.sdin().write_value(self.sdin.psel_bits()); + psel.sdout().write_value(self.sdout.psel_bits()); } fn setup_interrupt(&self) { @@ -720,7 +700,7 @@ impl<'d, T: Instance> I2S<'d, T> { /// I2S output pub struct OutputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, buffers: MultiBuffering, } @@ -774,7 +754,7 @@ impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> OutputStream< /// I2S input pub struct InputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, buffers: MultiBuffering, } @@ -829,7 +809,7 @@ impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> InputStream<' /// I2S full duplex stream (input & output) pub struct FullDuplexStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, buffers_out: MultiBuffering, buffers_in: MultiBuffering, } @@ -888,7 +868,7 @@ impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> FullDuplexStr } /// Helper encapsulating common I2S device operations. -struct Device(&'static RegisterBlock, PhantomData); +struct Device(pac::i2s::I2s, PhantomData); impl Device { fn new() -> Self { @@ -898,132 +878,132 @@ impl Device { #[inline(always)] pub fn enable(&self) { trace!("ENABLED"); - self.0.enable.write(|w| w.enable().enabled()); + self.0.enable().write(|w| w.set_enable(true)); } #[inline(always)] pub fn disable(&self) { trace!("DISABLED"); - self.0.enable.write(|w| w.enable().disabled()); + self.0.enable().write(|w| w.set_enable(false)); } #[inline(always)] fn enable_tx(&self) { trace!("TX ENABLED"); - self.0.config.txen.write(|w| w.txen().enabled()); + self.0.config().txen().write(|w| w.set_txen(true)); } #[inline(always)] fn disable_tx(&self) { trace!("TX DISABLED"); - self.0.config.txen.write(|w| w.txen().disabled()); + self.0.config().txen().write(|w| w.set_txen(false)); } #[inline(always)] fn enable_rx(&self) { trace!("RX ENABLED"); - self.0.config.rxen.write(|w| w.rxen().enabled()); + self.0.config().rxen().write(|w| w.set_rxen(true)); } #[inline(always)] fn disable_rx(&self) { trace!("RX DISABLED"); - self.0.config.rxen.write(|w| w.rxen().disabled()); + self.0.config().rxen().write(|w| w.set_rxen(false)); } #[inline(always)] fn start(&self) { trace!("START"); - self.0.tasks_start.write(|w| unsafe { w.bits(1) }); + self.0.tasks_start().write_value(1); } #[inline(always)] fn stop(&self) { - self.0.tasks_stop.write(|w| unsafe { w.bits(1) }); + self.0.tasks_stop().write_value(1); } #[inline(always)] fn is_stopped(&self) -> bool { - self.0.events_stopped.read().bits() != 0 + self.0.events_stopped().read() != 0 } #[inline(always)] fn reset_stopped_event(&self) { trace!("STOPPED EVENT: Reset"); - self.0.events_stopped.reset(); + self.0.events_stopped().write_value(0); } #[inline(always)] fn disable_stopped_interrupt(&self) { trace!("STOPPED INTERRUPT: Disabled"); - self.0.intenclr.write(|w| w.stopped().clear()); + self.0.intenclr().write(|w| w.set_stopped(true)); } #[inline(always)] fn enable_stopped_interrupt(&self) { trace!("STOPPED INTERRUPT: Enabled"); - self.0.intenset.write(|w| w.stopped().set()); + self.0.intenset().write(|w| w.set_stopped(true)); } #[inline(always)] fn reset_tx_ptr_event(&self) { trace!("TX PTR EVENT: Reset"); - self.0.events_txptrupd.reset(); + self.0.events_txptrupd().write_value(0); } #[inline(always)] fn reset_rx_ptr_event(&self) { trace!("RX PTR EVENT: Reset"); - self.0.events_rxptrupd.reset(); + self.0.events_rxptrupd().write_value(0); } #[inline(always)] fn disable_tx_ptr_interrupt(&self) { trace!("TX PTR INTERRUPT: Disabled"); - self.0.intenclr.write(|w| w.txptrupd().clear()); + self.0.intenclr().write(|w| w.set_txptrupd(true)); } #[inline(always)] fn disable_rx_ptr_interrupt(&self) { trace!("RX PTR INTERRUPT: Disabled"); - self.0.intenclr.write(|w| w.rxptrupd().clear()); + self.0.intenclr().write(|w| w.set_rxptrupd(true)); } #[inline(always)] fn enable_tx_ptr_interrupt(&self) { trace!("TX PTR INTERRUPT: Enabled"); - self.0.intenset.write(|w| w.txptrupd().set()); + self.0.intenset().write(|w| w.set_txptrupd(true)); } #[inline(always)] fn enable_rx_ptr_interrupt(&self) { trace!("RX PTR INTERRUPT: Enabled"); - self.0.intenset.write(|w| w.rxptrupd().set()); + self.0.intenset().write(|w| w.set_rxptrupd(true)); } #[inline(always)] fn is_tx_ptr_updated(&self) -> bool { - self.0.events_txptrupd.read().bits() != 0 + self.0.events_txptrupd().read() != 0 } #[inline(always)] fn is_rx_ptr_updated(&self) -> bool { - self.0.events_rxptrupd.read().bits() != 0 + self.0.events_rxptrupd().read() != 0 } #[inline] fn update_tx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; - self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); - self.0.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + self.0.rxtxd().maxcnt().write(|w| w.0 = maxcnt); + self.0.txd().ptr().write_value(ptr); Ok(()) } #[inline] fn update_rx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; - self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); - self.0.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + self.0.rxtxd().maxcnt().write(|w| w.0 = maxcnt); + self.0.rxd().ptr().write_value(ptr); Ok(()) } @@ -1160,13 +1140,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static crate::pac::i2s::RegisterBlock; + fn regs() -> pac::i2s::I2s; fn state() -> &'static State; } /// I2S peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -1174,8 +1154,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_i2s { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::i2s::SealedInstance for peripherals::$type { - fn regs() -> &'static crate::pac::i2s::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::i2s::I2s { + pac::$pac_type } fn state() -> &'static crate::i2s::State { static STATE: crate::i2s::State = crate::i2s::State::new(); diff --git a/embassy-nrf/src/ipc.rs b/embassy-nrf/src/ipc.rs new file mode 100644 index 000000000..a8a08c911 --- /dev/null +++ b/embassy-nrf/src/ipc.rs @@ -0,0 +1,363 @@ +//! InterProcessor Communication (IPC) + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac, ppi}; + +const EVENT_COUNT: usize = 16; + +/// IPC Event +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum EventNumber { + /// IPC Event 0 + Event0 = 0, + /// IPC Event 1 + Event1 = 1, + /// IPC Event 2 + Event2 = 2, + /// IPC Event 3 + Event3 = 3, + /// IPC Event 4 + Event4 = 4, + /// IPC Event 5 + Event5 = 5, + /// IPC Event 6 + Event6 = 6, + /// IPC Event 7 + Event7 = 7, + /// IPC Event 8 + Event8 = 8, + /// IPC Event 9 + Event9 = 9, + /// IPC Event 10 + Event10 = 10, + /// IPC Event 11 + Event11 = 11, + /// IPC Event 12 + Event12 = 12, + /// IPC Event 13 + Event13 = 13, + /// IPC Event 14 + Event14 = 14, + /// IPC Event 15 + Event15 = 15, +} + +const EVENTS: [EventNumber; EVENT_COUNT] = [ + EventNumber::Event0, + EventNumber::Event1, + EventNumber::Event2, + EventNumber::Event3, + EventNumber::Event4, + EventNumber::Event5, + EventNumber::Event6, + EventNumber::Event7, + EventNumber::Event8, + EventNumber::Event9, + EventNumber::Event10, + EventNumber::Event11, + EventNumber::Event12, + EventNumber::Event13, + EventNumber::Event14, + EventNumber::Event15, +]; + +/// IPC Channel +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum IpcChannel { + /// IPC Channel 0 + Channel0, + /// IPC Channel 1 + Channel1, + /// IPC Channel 2 + Channel2, + /// IPC Channel 3 + Channel3, + /// IPC Channel 4 + Channel4, + /// IPC Channel 5 + Channel5, + /// IPC Channel 6 + Channel6, + /// IPC Channel 7 + Channel7, + /// IPC Channel 8 + Channel8, + /// IPC Channel 9 + Channel9, + /// IPC Channel 10 + Channel10, + /// IPC Channel 11 + Channel11, + /// IPC Channel 12 + Channel12, + /// IPC Channel 13 + Channel13, + /// IPC Channel 14 + Channel14, + /// IPC Channel 15 + Channel15, +} + +impl IpcChannel { + fn mask(self) -> u32 { + 1 << (self as u32) + } +} + +/// Interrupt Handler +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + + // Check if an event was generated, and if it was, trigger the corresponding waker + for event in EVENTS { + if regs.events_receive(event as usize).read() & 0x01 == 0x01 { + regs.intenclr().write(|w| w.0 = 0x01 << event as u32); + T::state().wakers[event as usize].wake(); + } + } + } +} + +/// IPC driver +#[non_exhaustive] +pub struct Ipc<'d, T: Instance> { + /// Event 0 + pub event0: Event<'d, T>, + /// Event 1 + pub event1: Event<'d, T>, + /// Event 2 + pub event2: Event<'d, T>, + /// Event 3 + pub event3: Event<'d, T>, + /// Event 4 + pub event4: Event<'d, T>, + /// Event 5 + pub event5: Event<'d, T>, + /// Event 6 + pub event6: Event<'d, T>, + /// Event 7 + pub event7: Event<'d, T>, + /// Event 8 + pub event8: Event<'d, T>, + /// Event 9 + pub event9: Event<'d, T>, + /// Event 10 + pub event10: Event<'d, T>, + /// Event 11 + pub event11: Event<'d, T>, + /// Event 12 + pub event12: Event<'d, T>, + /// Event 13 + pub event13: Event<'d, T>, + /// Event 14 + pub event14: Event<'d, T>, + /// Event 15 + pub event15: Event<'d, T>, +} + +impl<'d, T: Instance> Ipc<'d, T> { + /// Create a new IPC driver. + pub fn new( + _p: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let _phantom = PhantomData; + #[rustfmt::skip] + let r = Self { // attributes on expressions are experimental + event0: Event { number: EventNumber::Event0, _phantom }, + event1: Event { number: EventNumber::Event1, _phantom }, + event2: Event { number: EventNumber::Event2, _phantom }, + event3: Event { number: EventNumber::Event3, _phantom }, + event4: Event { number: EventNumber::Event4, _phantom }, + event5: Event { number: EventNumber::Event5, _phantom }, + event6: Event { number: EventNumber::Event6, _phantom }, + event7: Event { number: EventNumber::Event7, _phantom }, + event8: Event { number: EventNumber::Event8, _phantom }, + event9: Event { number: EventNumber::Event9, _phantom }, + event10: Event { number: EventNumber::Event10, _phantom }, + event11: Event { number: EventNumber::Event11, _phantom }, + event12: Event { number: EventNumber::Event12, _phantom }, + event13: Event { number: EventNumber::Event13, _phantom }, + event14: Event { number: EventNumber::Event14, _phantom }, + event15: Event { number: EventNumber::Event15, _phantom }, + }; + r + } +} + +/// IPC event +pub struct Event<'d, T: Instance> { + number: EventNumber, + _phantom: PhantomData<&'d T>, +} + +impl<'d, T: Instance> Event<'d, T> { + /// Trigger the event. + pub fn trigger(&self) { + let nr = self.number; + T::regs().tasks_send(nr as usize).write_value(1); + } + + /// Wait for the event to be triggered. + pub async fn wait(&mut self) { + let regs = T::regs(); + let nr = self.number as usize; + regs.intenset().write(|w| w.0 = 1 << nr); + poll_fn(|cx| { + T::state().wakers[nr].register(cx.waker()); + + if regs.events_receive(nr).read() == 1 { + regs.events_receive(nr).write_value(0x00); + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + } + + /// Returns the [`EventNumber`] of the event. + pub fn number(&self) -> EventNumber { + self.number + } + + /// Create a handle that can trigger the event. + pub fn trigger_handle(&self) -> EventTrigger<'d, T> { + EventTrigger { + number: self.number, + _phantom: PhantomData, + } + } + + /// Configure the channels the event will broadcast to + pub fn configure_trigger>(&mut self, channels: I) { + T::regs().send_cnf(self.number as usize).write(|w| { + for channel in channels { + w.0 |= channel.mask(); + } + }) + } + + /// Configure the channels the event will listen on + pub fn configure_wait>(&mut self, channels: I) { + T::regs().receive_cnf(self.number as usize).write(|w| { + for channel in channels { + w.0 |= channel.mask(); + } + }); + } + + /// Get the task for the IPC event to use with PPI. + pub fn task(&self) -> ppi::Task<'d> { + let nr = self.number as usize; + let regs = T::regs(); + ppi::Task::from_reg(regs.tasks_send(nr)) + } + + /// Get the event for the IPC event to use with PPI. + pub fn event(&self) -> ppi::Event<'d> { + let nr = self.number as usize; + let regs = T::regs(); + ppi::Event::from_reg(regs.events_receive(nr)) + } + + /// Reborrow into a "child" Event. + /// + /// `self` will stay borrowed until the child Event is dropped. + pub fn reborrow(&mut self) -> Event<'_, T> { + Self { ..*self } + } + + /// Steal an IPC event by number. + /// + /// # Safety + /// + /// The event number must not be in use by another [`Event`]. + pub unsafe fn steal(number: EventNumber) -> Self { + Self { + number, + _phantom: PhantomData, + } + } +} + +/// A handle that can trigger an IPC event. +/// +/// This `struct` is returned by [`Event::trigger_handle`]. +#[derive(Debug, Copy, Clone)] +pub struct EventTrigger<'d, T: Instance> { + number: EventNumber, + _phantom: PhantomData<&'d T>, +} + +impl EventTrigger<'_, T> { + /// Trigger the event. + pub fn trigger(&self) { + let nr = self.number; + T::regs().tasks_send(nr as usize).write_value(1); + } + + /// Returns the [`EventNumber`] of the event. + pub fn number(&self) -> EventNumber { + self.number + } +} + +pub(crate) struct State { + wakers: [AtomicWaker; EVENT_COUNT], +} + +impl State { + pub(crate) const fn new() -> Self { + Self { + wakers: [const { AtomicWaker::new() }; EVENT_COUNT], + } + } +} + +pub(crate) trait SealedInstance { + fn regs() -> pac::ipc::Ipc; + fn state() -> &'static State; +} + +/// IPC peripheral instance. +#[allow(private_bounds)] +pub trait Instance: PeripheralType + SealedInstance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_ipc { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::ipc::SealedInstance for peripherals::$type { + fn regs() -> pac::ipc::Ipc { + pac::$pac_type + } + + fn state() -> &'static crate::ipc::State { + static STATE: crate::ipc::State = crate::ipc::State::new(); + &STATE + } + } + impl crate::ipc::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 13623dd1c..9d44ae7e6 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -11,7 +11,7 @@ #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] #[cfg(not(any( - feature = "nrf51", + feature = "_nrf51", feature = "nrf52805", feature = "nrf52810", feature = "nrf52811", @@ -22,6 +22,8 @@ feature = "nrf5340-app-s", feature = "nrf5340-app-ns", feature = "nrf5340-net", + feature = "nrf54l15-app-s", + feature = "nrf54l15-app-ns", feature = "nrf9160-s", feature = "nrf9160-ns", feature = "nrf9120-s", @@ -44,6 +46,8 @@ compile_error!( nrf5340-app-s, nrf5340-app-ns, nrf5340-net, + nrf54l15-app-s, + nrf54l15-app-ns, nrf9160-s, nrf9160-ns, nrf9120-s, @@ -61,6 +65,9 @@ compile_error!("feature `reset-pin-as-gpio` is only valid for nRF52 series chips #[cfg(all(feature = "nfc-pins-as-gpio", not(any(feature = "_nrf52", feature = "_nrf5340-app"))))] compile_error!("feature `nfc-pins-as-gpio` is only valid for nRF52, or nRF53's application core."); +#[cfg(all(feature = "lfxo-pins-as-gpio", not(feature = "_nrf5340")))] +compile_error!("feature `lfxo-pins-as-gpio` is only valid for nRF53 series chips."); + // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; pub(crate) mod util; @@ -68,21 +75,32 @@ pub(crate) mod util; #[cfg(feature = "_time-driver")] mod time_driver; -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] pub mod buffered_uarte; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] +pub mod egu; pub mod gpio; +#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(feature = "gpiote")] pub mod gpiote; - -// TODO: tested on other chips -#[cfg(not(any(feature = "_nrf91", feature = "_nrf5340-app")))] -pub mod radio; - -#[cfg(not(feature = "nrf51"))] -pub mod egu; +#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; +#[cfg(feature = "_nrf5340")] +pub mod ipc; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any( + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-app" +))] +pub mod nfct; +#[cfg(not(feature = "_nrf54l"))] // TODO pub mod nvmc; +#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any( feature = "nrf52810", feature = "nrf52811", @@ -93,35 +111,58 @@ pub mod nvmc; feature = "_nrf91", ))] pub mod pdm; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(any(feature = "nrf52840", feature = "nrf9160-s", feature = "nrf9160-ns"))] +pub mod power; +#[cfg(not(feature = "_nrf54l"))] // TODO pub mod ppi; +#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(any( - feature = "nrf51", + feature = "_nrf51", feature = "nrf52805", feature = "nrf52820", feature = "_nrf5340-net" )))] pub mod pwm; -#[cfg(not(any(feature = "nrf51", feature = "_nrf91", feature = "_nrf5340-net")))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf51", feature = "_nrf91", feature = "_nrf5340-net")))] pub mod qdec; +#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any(feature = "nrf52840", feature = "_nrf5340-app"))] pub mod qspi; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf91", feature = "_nrf5340-app")))] +pub mod radio; +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(feature = "_nrf5340")] +pub mod reset; +#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] pub mod rng; -#[cfg(not(any(feature = "nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] pub mod saadc; -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] pub mod spim; -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] pub mod spis; -#[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf91")))] pub mod temp; +#[cfg(not(feature = "_nrf54l"))] // TODO pub mod timer; -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] pub mod twim; -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] pub mod twis; -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf54l"))] // TODO +#[cfg(not(feature = "_nrf51"))] pub mod uarte; +#[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(any( feature = "_nrf5340-app", feature = "nrf52820", @@ -129,11 +170,11 @@ pub mod uarte; feature = "nrf52840" ))] pub mod usb; -#[cfg(not(feature = "_nrf5340"))] +#[cfg(not(feature = "_nrf54l"))] // TODO 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 = "_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")] @@ -143,6 +184,7 @@ pub mod wdt; #[cfg_attr(feature = "nrf52840", path = "chips/nrf52840.rs")] #[cfg_attr(feature = "_nrf5340-app", path = "chips/nrf5340_app.rs")] #[cfg_attr(feature = "_nrf5340-net", path = "chips/nrf5340_net.rs")] +#[cfg_attr(feature = "_nrf54l15-app", path = "chips/nrf54l15_app.rs")] #[cfg_attr(feature = "_nrf9160", path = "chips/nrf9160.rs")] #[cfg_attr(feature = "_nrf9120", path = "chips/nrf9120.rs")] mod chip; @@ -158,9 +200,12 @@ mod chip; /// ```rust,ignore /// use embassy_nrf::{bind_interrupts, spim, peripherals}; /// -/// bind_interrupts!(struct Irqs { -/// SPIM3 => spim::InterruptHandler; -/// }); +/// bind_interrupts!( +/// /// Binds the SPIM3 interrupt. +/// struct Irqs { +/// SPIM3 => spim::InterruptHandler; +/// } +/// ); /// ``` /// /// Example of how to bind multiple interrupts in a single macro invocation: @@ -170,32 +215,51 @@ mod chip; /// /// bind_interrupts!(struct Irqs { /// SPIM3 => spim::InterruptHandler; -/// SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler; +/// TWISPI0 => twim::InterruptHandler; /// }); /// ``` // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { - #[derive(Copy, Clone)] - $vis struct $name; - - $( - #[allow(non_snake_case)] - #[no_mangle] - unsafe extern "C" fn $irq() { - $( - <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); - )* - } + ($(#[$attr:meta])* $vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + )* + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} )* - )* - }; + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* } +} // Reexports @@ -204,9 +268,10 @@ pub use chip::pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use chip::pac; pub use chip::{peripherals, Peripherals, EASY_DMA_SIZE}; -pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use embassy_hal_internal::{Peri, PeripheralType}; pub use crate::chip::interrupt; +#[cfg(feature = "rt")] pub use crate::pac::NVIC_PRIO_BITS; pub mod config { @@ -225,15 +290,16 @@ pub mod config { /// Internal RC oscillator InternalRC, /// Synthesized from the high frequency clock source. - #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + #[cfg(not(feature = "_nrf91"))] Synthesized, /// External source from xtal. + #[cfg(not(feature = "lfxo-pins-as-gpio"))] ExternalXtal, /// External source from xtal with low swing applied. - #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + #[cfg(not(any(feature = "lfxo-pins-as-gpio", feature = "_nrf91", feature = "_nrf54l")))] ExternalLowSwing, /// External source from xtal with full swing applied. - #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + #[cfg(not(any(feature = "lfxo-pins-as-gpio", feature = "_nrf91", feature = "_nrf54l")))] ExternalFullSwing, } @@ -257,14 +323,14 @@ pub mod config { #[cfg(feature = "nrf52840")] pub reg0: bool, /// Configure the voltage of the first stage DCDC. It is stored in non-volatile memory (UICR.REGOUT0 register); pass None to not touch it. - #[cfg(feature = "nrf52840")] + #[cfg(any(feature = "nrf52840", feature = "nrf52833"))] pub reg0_voltage: Option, /// Config for the second stage DCDC (VDD -> DEC4), if disabled LDO will be used. pub reg1: bool, } /// Output voltage setting for REG0 regulator stage. - #[cfg(feature = "nrf52840")] + #[cfg(any(feature = "nrf52840", feature = "nrf52833"))] pub enum Reg0Voltage { /// 1.8 V _1V8 = 0, @@ -277,7 +343,7 @@ pub mod config { /// 3.0 V _3V0 = 4, /// 3.3 V - _3v3 = 5, + _3V3 = 5, //ERASED = 7, means 1.8V } @@ -286,12 +352,33 @@ pub mod config { pub struct DcdcConfig { /// Config for the high voltage stage, if disabled LDO will be used. pub regh: bool, + /// Configure the voltage of the high voltage stage. It is stored in non-volatile memory (UICR.VREGHVOUT register); pass None to not touch it. + #[cfg(feature = "nrf5340-app-s")] + pub regh_voltage: Option, /// Config for the main rail, if disabled LDO will be used. pub regmain: bool, /// Config for the radio rail, if disabled LDO will be used. pub regradio: bool, } + /// Output voltage setting for VREGH regulator stage. + #[cfg(feature = "nrf5340-app-s")] + pub enum ReghVoltage { + /// 1.8 V + _1V8 = 0, + /// 2.1 V + _2V1 = 1, + /// 2.4 V + _2V4 = 2, + /// 2.7 V + _2V7 = 3, + /// 3.0 V + _3V0 = 4, + /// 3.3 V + _3V3 = 5, + //ERASED = 7, means 1.8V + } + /// Settings for enabling the built in DCDC converter. #[cfg(feature = "_nrf91")] pub struct DcdcConfig { @@ -299,6 +386,133 @@ pub mod config { pub regmain: bool, } + /// Settings for the internal capacitors. + #[cfg(feature = "nrf5340-app-s")] + pub struct InternalCapacitors { + /// Config for the internal capacitors on pins XC1 and XC2. + pub hfxo: Option, + /// Config for the internal capacitors between pins XL1 and XL2. + pub lfxo: Option, + } + + /// Internal capacitance value for the HFXO. + #[cfg(feature = "nrf5340-app-s")] + #[derive(Copy, Clone)] + pub enum HfxoCapacitance { + /// 7.0 pF + _7_0pF, + /// 7.5 pF + _7_5pF, + /// 8.0 pF + _8_0pF, + /// 8.5 pF + _8_5pF, + /// 9.0 pF + _9_0pF, + /// 9.5 pF + _9_5pF, + /// 10.0 pF + _10_0pF, + /// 10.5 pF + _10_5pF, + /// 11.0 pF + _11_0pF, + /// 11.5 pF + _11_5pF, + /// 12.0 pF + _12_0pF, + /// 12.5 pF + _12_5pF, + /// 13.0 pF + _13_0pF, + /// 13.5 pF + _13_5pF, + /// 14.0 pF + _14_0pF, + /// 14.5 pF + _14_5pF, + /// 15.0 pF + _15_0pF, + /// 15.5 pF + _15_5pF, + /// 16.0 pF + _16_0pF, + /// 16.5 pF + _16_5pF, + /// 17.0 pF + _17_0pF, + /// 17.5 pF + _17_5pF, + /// 18.0 pF + _18_0pF, + /// 18.5 pF + _18_5pF, + /// 19.0 pF + _19_0pF, + /// 19.5 pF + _19_5pF, + /// 20.0 pF + _20_0pF, + } + + #[cfg(feature = "nrf5340-app-s")] + impl HfxoCapacitance { + /// The capacitance value times two. + pub(crate) const fn value2(self) -> i32 { + match self { + HfxoCapacitance::_7_0pF => 14, + HfxoCapacitance::_7_5pF => 15, + HfxoCapacitance::_8_0pF => 16, + HfxoCapacitance::_8_5pF => 17, + HfxoCapacitance::_9_0pF => 18, + HfxoCapacitance::_9_5pF => 19, + HfxoCapacitance::_10_0pF => 20, + HfxoCapacitance::_10_5pF => 21, + HfxoCapacitance::_11_0pF => 22, + HfxoCapacitance::_11_5pF => 23, + HfxoCapacitance::_12_0pF => 24, + HfxoCapacitance::_12_5pF => 25, + HfxoCapacitance::_13_0pF => 26, + HfxoCapacitance::_13_5pF => 27, + HfxoCapacitance::_14_0pF => 28, + HfxoCapacitance::_14_5pF => 29, + HfxoCapacitance::_15_0pF => 30, + HfxoCapacitance::_15_5pF => 31, + HfxoCapacitance::_16_0pF => 32, + HfxoCapacitance::_16_5pF => 33, + HfxoCapacitance::_17_0pF => 34, + HfxoCapacitance::_17_5pF => 35, + HfxoCapacitance::_18_0pF => 36, + HfxoCapacitance::_18_5pF => 37, + HfxoCapacitance::_19_0pF => 38, + HfxoCapacitance::_19_5pF => 39, + HfxoCapacitance::_20_0pF => 40, + } + } + } + + /// Internal capacitance value for the LFXO. + #[cfg(feature = "nrf5340-app-s")] + pub enum LfxoCapacitance { + /// 6 pF + _6pF = 1, + /// 7 pF + _7pF = 2, + /// 9 pF + _9pF = 3, + } + + #[cfg(feature = "nrf5340-app-s")] + impl From for super::pac::oscillators::vals::Intcap { + fn from(t: LfxoCapacitance) -> Self { + match t { + LfxoCapacitance::_6pF => Self::C6PF, + LfxoCapacitance::_7pF => Self::C7PF, + LfxoCapacitance::_9pF => Self::C9PF, + } + } + } + /// Configuration for peripherals. Default configuration should work on any nRF chip. #[non_exhaustive] pub struct Config { @@ -306,7 +520,11 @@ pub mod config { pub hfclk_source: HfclkSource, /// Low frequency clock source. pub lfclk_source: LfclkSource, - #[cfg(not(feature = "_nrf5340-net"))] + #[cfg(feature = "nrf5340-app-s")] + /// Internal capacitor configuration, for use with the `ExternalXtal` clock source. See + /// nrf5340-PS §4.12. + pub internal_capacitors: InternalCapacitors, + #[cfg(not(any(feature = "_nrf5340-net", feature = "_nrf54l")))] /// DCDC configuration. pub dcdc: DcdcConfig, /// GPIOTE interrupt priority. Should be lower priority than softdevice if used. @@ -327,17 +545,21 @@ pub mod config { // xtals if they know they have them. hfclk_source: HfclkSource::Internal, lfclk_source: LfclkSource::InternalRC, - #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + #[cfg(feature = "nrf5340-app-s")] + internal_capacitors: InternalCapacitors { hfxo: None, lfxo: None }, + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] dcdc: DcdcConfig { #[cfg(feature = "nrf52840")] reg0: false, - #[cfg(feature = "nrf52840")] + #[cfg(any(feature = "nrf52840", feature = "nrf52833"))] reg0_voltage: None, reg1: false, }, #[cfg(feature = "_nrf5340-app")] dcdc: DcdcConfig { regh: false, + #[cfg(feature = "nrf5340-app-s")] + regh_voltage: None, regmain: false, regradio: false, }, @@ -370,6 +592,7 @@ mod consts { #[allow(unused)] mod consts { pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_VREGHVOUT: *mut u32 = 0x00FF8010 as *mut u32; pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF801C as *mut u32; pub const UICR_NFCPINS: *mut u32 = 0x00FF8028 as *mut u32; pub const APPROTECT_ENABLED: u32 = 0x0000_0000; @@ -396,7 +619,7 @@ mod consts { pub const APPROTECT_DISABLED: u32 = 0x0000_005a; } -#[cfg(not(feature = "nrf51"))] +#[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum WriteResult { @@ -408,12 +631,12 @@ enum WriteResult { Failed, } -#[cfg(not(feature = "nrf51"))] +#[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult { uicr_write_masked(address, value, 0xFFFF_FFFF) } -#[cfg(not(feature = "nrf51"))] +#[cfg(not(any(feature = "_nrf51", feature = "_nrf54l")))] 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 { @@ -425,13 +648,13 @@ unsafe fn uicr_write_masked(address: *mut u32, value: u32, mask: u32) -> WriteRe return WriteResult::Failed; } - let nvmc = &*pac::NVMC::ptr(); - nvmc.config.write(|w| w.wen().wen()); - while nvmc.ready.read().ready().is_busy() {} + let nvmc = pac::NVMC; + nvmc.config().write(|w| w.set_wen(pac::nvmc::vals::Wen::WEN)); + while !nvmc.ready().read().ready() {} address.write_volatile(value | !mask); - while nvmc.ready.read().ready().is_busy() {} - nvmc.config.reset(); - while nvmc.ready.read().ready().is_busy() {} + while !nvmc.ready().read().ready() {} + nvmc.config().write(|_| {}); + while !nvmc.ready().read().ready() {} WriteResult::Written } @@ -450,7 +673,7 @@ pub fn init(config: config::Config) -> Peripherals { let mut needs_reset = false; // Setup debug protection. - #[cfg(not(feature = "nrf51"))] + #[cfg(not(feature = "_nrf51"))] match config.debug { config::Debug::Allowed => { #[cfg(feature = "_nrf52")] @@ -477,32 +700,91 @@ pub fn init(config: config::Config) -> Peripherals { #[cfg(feature = "_nrf5340")] unsafe { - let p = &*pac::CTRLAP::ptr(); + let p = pac::CTRLAP; let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); needs_reset |= res == WriteResult::Written; - p.approtect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); + p.approtect().disable().write_value(consts::APPROTECT_DISABLED); #[cfg(feature = "_nrf5340-app")] { let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); needs_reset |= res == WriteResult::Written; - p.secureapprotect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); + p.secureapprotect().disable().write_value(consts::APPROTECT_DISABLED); } } + // TAMPC is only accessible for secure code + #[cfg(all(feature = "_nrf54l", feature = "_s"))] + { + use crate::pac::tampc::vals; + + // UICR cannot be written here, because it can only be written once after an erase all. + // The erase all value means that debug access is allowed if permitted by the firmware. + + // Write to TAMPC to permit debug access + // + // See https://docs.nordicsemi.com/bundle/ps_nrf54L15/page/debug.html#ariaid-title6 + + let p = pac::TAMPC; + + // Unlock dbgen + p.protect().domain(0).dbgen().ctrl().write(|w| { + w.set_key(vals::DomainDbgenCtrlKey::KEY); + w.set_writeprotection(vals::DomainDbgenCtrlWriteprotection::CLEAR); + }); + p.protect().domain(0).dbgen().ctrl().write(|w| { + w.set_key(vals::DomainDbgenCtrlKey::KEY); + w.set_value(vals::DomainDbgenCtrlValue::HIGH); + }); + + // Unlock niden + p.protect().domain(0).niden().ctrl().write(|w| { + w.set_key(vals::NidenCtrlKey::KEY); + w.set_writeprotection(vals::NidenCtrlWriteprotection::CLEAR); + }); + p.protect().domain(0).niden().ctrl().write(|w| { + w.set_key(vals::NidenCtrlKey::KEY); + w.set_value(vals::NidenCtrlValue::HIGH); + }); + + p.protect().domain(0).spiden().ctrl().write(|w| { + w.set_key(vals::SpidenCtrlKey::KEY); + w.set_writeprotection(vals::SpidenCtrlWriteprotection::CLEAR); + }); + p.protect().domain(0).spiden().ctrl().write(|w| { + w.set_key(vals::SpidenCtrlKey::KEY); + w.set_value(vals::SpidenCtrlValue::HIGH); + }); + + p.protect().domain(0).spniden().ctrl().write(|w| { + w.set_key(vals::SpnidenCtrlKey::KEY); + w.set_writeprotection(vals::SpnidenCtrlWriteprotection::CLEAR); + }); + p.protect().domain(0).spniden().ctrl().write(|w| { + w.set_key(vals::SpnidenCtrlKey::KEY); + w.set_value(vals::SpnidenCtrlValue::HIGH); + }); + } + // nothing to do on the nrf9160, debug is allowed by default. } - config::Debug::Disallowed => unsafe { - // UICR.APPROTECT = Enabled - let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); - needs_reset |= res == WriteResult::Written; - #[cfg(any(feature = "_nrf5340-app", feature = "_nrf91"))] - { - let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + config::Debug::Disallowed => { + // TODO: Handle nRF54L + // By default, debug access is not allowed if the firmware doesn't allow it. + // Code could be added here to disable debug access in UICR as well. + #[cfg(not(feature = "_nrf54l"))] + unsafe { + // UICR.APPROTECT = Enabled + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); needs_reset |= res == WriteResult::Written; + #[cfg(any(feature = "_nrf5340-app", feature = "_nrf91"))] + { + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; + } } - }, + } config::Debug::NotConfigured => {} } @@ -548,7 +830,7 @@ pub fn init(config: config::Config) -> Peripherals { } } - #[cfg(feature = "nrf52840")] + #[cfg(any(feature = "nrf52840", feature = "nrf52833"))] unsafe { if let Some(value) = config.dcdc.reg0_voltage { let value = value as u32; @@ -563,93 +845,183 @@ pub fn init(config: config::Config) -> Peripherals { } } + #[cfg(feature = "nrf5340-app-s")] + unsafe { + if let Some(value) = config.dcdc.regh_voltage { + let value = value as u32; + let res = uicr_write_masked(consts::UICR_VREGHVOUT, value, 0b00000000_00000000_00000000_00000111); + needs_reset |= res == WriteResult::Written; + if res == WriteResult::Failed { + warn!( + "Failed to set regulator voltage, as UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + } + if needs_reset { cortex_m::peripheral::SCB::sys_reset(); } - let r = unsafe { &*pac::CLOCK::ptr() }; + // Configure internal capacitors + #[cfg(feature = "nrf5340-app-s")] + { + if let Some(cap) = config.internal_capacitors.hfxo { + let mut slope = pac::FICR.xosc32mtrim().read().slope() as i32; + let offset = pac::FICR.xosc32mtrim().read().offset() as i32; + // slope is a signed 5-bit integer + if slope >= 16 { + slope -= 32; + } + let capvalue = (((slope + 56) * (cap.value2() - 14)) + ((offset - 8) << 4) + 32) >> 6; + pac::OSCILLATORS.xosc32mcaps().write(|w| { + w.set_capvalue(capvalue as u8); + w.set_enable(true); + }); + } + if let Some(cap) = config.internal_capacitors.lfxo { + pac::OSCILLATORS.xosc32ki().intcap().write(|w| w.set_intcap(cap.into())); + } + } + + let r = pac::CLOCK; // Start HFCLK. match config.hfclk_source { config::HfclkSource::Internal => {} config::HfclkSource::ExternalXtal => { - // Datasheet says this is likely to take 0.36ms - r.events_hfclkstarted.write(|w| unsafe { w.bits(0) }); - r.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while r.events_hfclkstarted.read().bits() == 0 {} + #[cfg(feature = "_nrf54l")] + { + r.events_xostarted().write_value(0); + r.tasks_xostart().write_value(1); + while r.events_xostarted().read() == 0 {} + } + + #[cfg(not(feature = "_nrf54l"))] + { + // Datasheet says this is likely to take 0.36ms + r.events_hfclkstarted().write_value(0); + r.tasks_hfclkstart().write_value(1); + while r.events_hfclkstarted().read() == 0 {} + } } } + // Workaround for anomaly 140 + #[cfg(feature = "nrf5340-app-s")] + if unsafe { (0x50032420 as *mut u32).read_volatile() } & 0x80000000 != 0 { + r.events_lfclkstarted().write_value(0); + r.lfclksrc() + .write(|w| w.set_src(nrf_pac::clock::vals::Lfclksrc::LFSYNT)); + r.tasks_lfclkstart().write_value(1); + while r.events_lfclkstarted().read() == 0 {} + r.events_lfclkstarted().write_value(0); + r.tasks_lfclkstop().write_value(1); + r.lfclksrc().write(|w| w.set_src(nrf_pac::clock::vals::Lfclksrc::LFRC)); + } + // Configure LFCLK. - #[cfg(not(any(feature = "nrf51", feature = "_nrf5340", feature = "_nrf91")))] + #[cfg(not(any(feature = "_nrf51", feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] match config.lfclk_source { - config::LfclkSource::InternalRC => r.lfclksrc.write(|w| w.src().rc()), - config::LfclkSource::Synthesized => r.lfclksrc.write(|w| w.src().synth()), - - config::LfclkSource::ExternalXtal => r.lfclksrc.write(|w| w.src().xtal()), - - config::LfclkSource::ExternalLowSwing => r.lfclksrc.write(|w| { - w.src().xtal(); - w.external().enabled(); - w.bypass().disabled(); - w + config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::RC)), + config::LfclkSource::Synthesized => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::SYNTH)), + config::LfclkSource::ExternalXtal => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::XTAL)), + config::LfclkSource::ExternalLowSwing => r.lfclksrc().write(|w| { + w.set_src(pac::clock::vals::Lfclksrc::XTAL); + w.set_external(true); + w.set_bypass(false); }), - config::LfclkSource::ExternalFullSwing => r.lfclksrc.write(|w| { - w.src().xtal(); - w.external().enabled(); - w.bypass().enabled(); - w + config::LfclkSource::ExternalFullSwing => r.lfclksrc().write(|w| { + w.set_src(pac::clock::vals::Lfclksrc::XTAL); + w.set_external(true); + w.set_bypass(true); }), } + #[cfg(feature = "_nrf5340")] + { + #[allow(unused_mut)] + let mut lfxo = false; + match config.lfclk_source { + config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFRC)), + config::LfclkSource::Synthesized => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFSYNT)), + #[cfg(not(feature = "lfxo-pins-as-gpio"))] + config::LfclkSource::ExternalXtal => lfxo = true, + #[cfg(not(feature = "lfxo-pins-as-gpio"))] + config::LfclkSource::ExternalLowSwing => lfxo = true, + #[cfg(not(feature = "lfxo-pins-as-gpio"))] + config::LfclkSource::ExternalFullSwing => { + #[cfg(feature = "_nrf5340-app")] + pac::OSCILLATORS.xosc32ki().bypass().write(|w| w.set_bypass(true)); + lfxo = true; + } + } + if lfxo { + if cfg!(feature = "_s") { + // MCUSEL is only accessible from secure code. + let p0 = pac::P0; + p0.pin_cnf(0) + .write(|w| w.set_mcusel(pac::gpio::vals::Mcusel::PERIPHERAL)); + p0.pin_cnf(1) + .write(|w| w.set_mcusel(pac::gpio::vals::Mcusel::PERIPHERAL)); + } + r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFXO)); + } + } #[cfg(feature = "_nrf91")] match config.lfclk_source { - config::LfclkSource::InternalRC => r.lfclksrc.write(|w| w.src().lfrc()), - config::LfclkSource::ExternalXtal => r.lfclksrc.write(|w| w.src().lfxo()), + config::LfclkSource::InternalRC => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFRC)), + config::LfclkSource::ExternalXtal => r.lfclksrc().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFXO)), + } + #[cfg(feature = "_nrf54l")] + match config.lfclk_source { + config::LfclkSource::InternalRC => r.lfclk().src().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFRC)), + config::LfclkSource::Synthesized => r.lfclk().src().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFSYNT)), + config::LfclkSource::ExternalXtal => r.lfclk().src().write(|w| w.set_src(pac::clock::vals::Lfclksrc::LFXO)), } // Start LFCLK. // Datasheet says this could take 100us from synth source // 600us from rc source, 0.25s from an external source. - r.events_lfclkstarted.write(|w| unsafe { w.bits(0) }); - r.tasks_lfclkstart.write(|w| unsafe { w.bits(1) }); - while r.events_lfclkstarted.read().bits() == 0 {} + r.events_lfclkstarted().write_value(0); + r.tasks_lfclkstart().write_value(1); + while r.events_lfclkstarted().read() == 0 {} - #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91")))] + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf91", feature = "_nrf54l")))] { // Setup DCDCs. - let pwr = unsafe { &*pac::POWER::ptr() }; #[cfg(feature = "nrf52840")] if config.dcdc.reg0 { - pwr.dcdcen0.write(|w| w.dcdcen().set_bit()); + pac::POWER.dcdcen0().write(|w| w.set_dcdcen(true)); } if config.dcdc.reg1 { - pwr.dcdcen.write(|w| w.dcdcen().set_bit()); + pac::POWER.dcdcen().write(|w| w.set_dcdcen(true)); } } #[cfg(feature = "_nrf91")] { // Setup DCDC. - let reg = unsafe { &*pac::REGULATORS::ptr() }; if config.dcdc.regmain { - reg.dcdcen.write(|w| w.dcdcen().set_bit()); + pac::REGULATORS.dcdcen().write(|w| w.set_dcdcen(true)); } } #[cfg(feature = "_nrf5340-app")] { // Setup DCDC. - let reg = unsafe { &*pac::REGULATORS::ptr() }; + let reg = pac::REGULATORS; if config.dcdc.regh { - reg.vregh.dcdcen.write(|w| w.dcdcen().set_bit()); + reg.vregh().dcdcen().write(|w| w.set_dcdcen(true)); } if config.dcdc.regmain { - reg.vregmain.dcdcen.write(|w| w.dcdcen().set_bit()); + reg.vregmain().dcdcen().write(|w| w.set_dcdcen(true)); } if config.dcdc.regradio { - reg.vregradio.dcdcen.write(|w| w.dcdcen().set_bit()); + reg.vregradio().dcdcen().write(|w| w.set_dcdcen(true)); } } // Init GPIOTE + #[cfg(not(feature = "_nrf54l"))] // TODO #[cfg(feature = "gpiote")] gpiote::init(config.gpiote_interrupt_priority); @@ -659,10 +1031,35 @@ pub fn init(config: config::Config) -> Peripherals { // Disable UARTE (enabled by default for some reason) #[cfg(feature = "_nrf91")] - unsafe { - (*pac::UARTE0::ptr()).enable.write(|w| w.enable().disabled()); - (*pac::UARTE1::ptr()).enable.write(|w| w.enable().disabled()); + { + use pac::uarte::vals::Enable; + pac::UARTE0.enable().write(|w| w.set_enable(Enable::DISABLED)); + pac::UARTE1.enable().write(|w| w.set_enable(Enable::DISABLED)); } peripherals } + +/// Operating modes for peripherals. +pub mod mode { + trait SealedMode {} + + /// Operating mode for a peripheral. + #[allow(private_bounds)] + pub trait Mode: SealedMode {} + + macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; + } + + /// Blocking mode. + pub struct Blocking; + /// Async mode. + pub struct Async; + + impl_mode!(Blocking); + impl_mode!(Async); +} diff --git a/embassy-nrf/src/nfct.rs b/embassy-nrf/src/nfct.rs new file mode 100644 index 000000000..8d70ec954 --- /dev/null +++ b/embassy-nrf/src/nfct.rs @@ -0,0 +1,425 @@ +//! NFC tag emulator driver. +//! +//! This driver implements support for emulating an ISO14443-3 card. Anticollision and selection +//! are handled automatically in hardware, then the driver lets you receive and reply to +//! raw ISO14443-3 frames in software. +//! +//! Higher layers such as ISO14443-4 aka ISO-DEP and ISO7816 must be handled on top +//! in software. + +#![macro_use] + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; +pub use vals::{Bitframesdd as SddPat, Discardmode as DiscardMode}; + +use crate::interrupt::InterruptExt; +use crate::pac::nfct::vals; +use crate::pac::NFCT; +use crate::peripherals::NFCT; +use crate::util::slice_in_ram; +use crate::{interrupt, pac, Peri}; + +/// NFCID1 (aka UID) of different sizes. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum NfcId { + /// 4-byte UID. + SingleSize([u8; 4]), + /// 7-byte UID. + DoubleSize([u8; 7]), + /// 10-byte UID. + TripleSize([u8; 10]), +} + +/// The protocol field to be sent in the `SEL_RES` response byte (b6-b7). +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum SelResProtocol { + /// Configured for Type 2 Tag platform. + #[default] + Type2 = 0, + /// Configured for Type 4A Tag platform, compliant with ISO/IEC_14443. + Type4A = 1, + /// Configured for the NFC-DEP Protocol. + NfcDep = 2, + /// Configured for the NFC-DEP Protocol and Type 4A Tag platform. + NfcDepAndType4A = 3, +} + +/// Config for the `NFCT` peripheral driver. +#[derive(Clone)] +pub struct Config { + /// NFCID1 to use during autocollision. + pub nfcid1: NfcId, + /// SDD pattern to be sent in `SENS_RES`. + pub sdd_pat: SddPat, + /// Platform config to be sent in `SEL_RES`. + pub plat_conf: u8, + /// Protocol to be sent in the `SEL_RES` response. + pub protocol: SelResProtocol, +} + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + trace!("irq"); + pac::NFCT.inten().write(|w| w.0 = 0); + WAKER.wake(); + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// NFC error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Rx Error received while waiting for frame + RxError, + /// Rx buffer was overrun, increase your buffer size to resolve this + RxOverrun, + /// Lost field. + Deactivated, + /// Collision + Collision, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, +} + +/// NFC tag emulator driver. +pub struct NfcT<'d> { + _p: Peri<'d, NFCT>, + rx_buf: [u8; 256], + tx_buf: [u8; 256], +} + +impl<'d> NfcT<'d> { + /// Create an Nfc Tag driver + pub fn new( + _p: Peri<'d, NFCT>, + _irq: impl interrupt::typelevel::Binding + 'd, + config: &Config, + ) -> Self { + let r = pac::NFCT; + + unsafe { + let reset = (r.as_ptr() as *mut u32).add(0xFFC / 4); + reset.write_volatile(0); + reset.read_volatile(); + reset.write_volatile(1); + } + + let nfcid_size = match &config.nfcid1 { + NfcId::SingleSize(bytes) => { + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*bytes)); + + vals::Nfcidsize::NFCID1SINGLE + } + NfcId::DoubleSize(bytes) => { + let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap(); + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk)); + + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(bytes); + r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + vals::Nfcidsize::NFCID1DOUBLE + } + NfcId::TripleSize(bytes) => { + let (bytes, chunk) = bytes.split_last_chunk::<4>().unwrap(); + r.nfcid1_last().write(|w| w.0 = u32::from_be_bytes(*chunk)); + + let (bytes, chunk2) = bytes.split_last_chunk::<3>().unwrap(); + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(chunk2); + r.nfcid1_2nd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + let mut chunk = [0u8; 4]; + chunk[1..].copy_from_slice(bytes); + r.nfcid1_3rd_last().write(|w| w.0 = u32::from_be_bytes(chunk)); + + vals::Nfcidsize::NFCID1TRIPLE + } + }; + + r.sensres().write(|w| { + w.set_nfcidsize(nfcid_size); + w.set_bitframesdd(config.sdd_pat); + w.set_platfconfig(config.plat_conf & 0xF); + }); + + r.selres().write(|w| { + w.set_protocol(config.protocol as u8); + }); + + // errata + #[cfg(feature = "nrf52832")] + unsafe { + // Errata 57 nrf52832 only + //(0x40005610 as *mut u32).write_volatile(0x00000005); + //(0x40005688 as *mut u32).write_volatile(0x00000001); + //(0x40005618 as *mut u32).write_volatile(0x00000000); + //(0x40005614 as *mut u32).write_volatile(0x0000003F); + + // Errata 98 + (0x4000568C as *mut u32).write_volatile(0x00038148); + } + + r.inten().write(|w| w.0 = 0); + + interrupt::NFCT.unpend(); + unsafe { interrupt::NFCT.enable() }; + + // clear all shorts + r.shorts().write(|_| {}); + + let res = Self { + _p, + tx_buf: [0u8; 256], + rx_buf: [0u8; 256], + }; + + assert!(slice_in_ram(&res.tx_buf), "TX Buf not in ram"); + assert!(slice_in_ram(&res.rx_buf), "RX Buf not in ram"); + + res + } + + /// Wait for field on and select. + /// + /// This waits for the field to become on, and then for a reader to select us. The ISO14443-3 + /// sense, anticollision and select procedure is handled entirely in hardware. + /// + /// When this returns, we have successfully been selected as a card. You must then + /// loop calling [`receive`](Self::receive) and responding with [`transmit`](Self::transmit). + pub async fn activate(&mut self) { + let r = pac::NFCT; + loop { + r.events_fieldlost().write_value(0); + r.events_fielddetected().write_value(0); + r.tasks_sense().write_value(1); + + // enable autocoll + #[cfg(not(feature = "nrf52832"))] + r.autocolresconfig().write(|w| w.0 = 0b10); + + // framedelaymax=4096 is needed to make it work with phones from + // a certain company named after some fruit. + r.framedelaymin().write(|w| w.set_framedelaymin(1152)); + r.framedelaymax().write(|w| w.set_framedelaymax(4096)); + r.framedelaymode().write(|w| { + w.set_framedelaymode(vals::Framedelaymode::WINDOW_GRID); + }); + + info!("waiting for field"); + poll_fn(|cx| { + WAKER.register(cx.waker()); + + if r.events_fielddetected().read() != 0 { + r.events_fielddetected().write_value(0); + return Poll::Ready(()); + } + + r.inten().write(|w| { + w.set_fielddetected(true); + }); + Poll::Pending + }) + .await; + + #[cfg(feature = "time")] + embassy_time::Timer::after_millis(1).await; // workaround errata 190 + + r.events_selected().write_value(0); + r.tasks_activate().write_value(1); + + trace!("Waiting to be selected"); + poll_fn(|cx| { + let r = pac::NFCT; + + WAKER.register(cx.waker()); + + if r.events_selected().read() != 0 || r.events_fieldlost().read() != 0 { + return Poll::Ready(()); + } + + r.inten().write(|w| { + w.set_selected(true); + w.set_fieldlost(true); + }); + Poll::Pending + }) + .await; + if r.events_fieldlost().read() != 0 { + continue; + } + + // disable autocoll + #[cfg(not(feature = "nrf52832"))] + r.autocolresconfig().write(|w| w.0 = 0b11u32); + + // once anticoll is done, set framedelaymax to the maximum possible. + // this gives the firmware as much time as possible to reply. + // higher layer still has to reply faster than the FWT it specifies in the iso14443-4 ATS, + // but that's not our concern. + // + // nrf52832 field is 16bit instead of 20bit. this seems to force a too short timeout, maybe it's a SVD bug? + #[cfg(not(feature = "nrf52832"))] + r.framedelaymax().write(|w| w.set_framedelaymax(0xF_FFFF)); + #[cfg(feature = "nrf52832")] + r.framedelaymax().write(|w| w.set_framedelaymax(0xFFFF)); + + return; + } + } + + /// Transmit an ISO14443-3 frame to the reader. + /// + /// You must call this only after receiving a frame with [`receive`](Self::receive), + /// and only once. Higher-layer protocols usually define timeouts, so calling this + /// too late can cause things to fail. + /// + /// This will fail with [`Error::Deactivated`] if we have been deselected due to either + /// the field being switched off or due to the ISO14443 state machine. When this happens, + /// you must stop calling [`receive`](Self::receive) and [`transmit`](Self::transmit), reset + /// all protocol state, and go back to calling [`activate`](Self::activate). + pub async fn transmit(&mut self, buf: &[u8]) -> Result<(), Error> { + let r = pac::NFCT; + + //Setup DMA + self.tx_buf[..buf.len()].copy_from_slice(buf); + r.packetptr().write_value(self.tx_buf.as_ptr() as u32); + r.maxlen().write(|w| w.0 = buf.len() as _); + + // Set packet length + r.txd().amount().write(|w| { + w.set_txdatabits(0); + w.set_txdatabytes(buf.len() as _); + }); + + r.txd().frameconfig().write(|w| { + w.set_crcmodetx(true); + w.set_discardmode(DiscardMode::DISCARD_END); + w.set_parity(true); + w.set_sof(true); + }); + + r.events_error().write_value(0); + r.events_txframeend().write_value(0); + r.errorstatus().write(|w| w.0 = 0xffff_ffff); + + // Start starttx task + compiler_fence(Ordering::SeqCst); + r.tasks_starttx().write_value(1); + + poll_fn(move |cx| { + trace!("polling tx"); + let r = pac::NFCT; + WAKER.register(cx.waker()); + + if r.events_fieldlost().read() != 0 { + return Poll::Ready(Err(Error::Deactivated)); + } + + if r.events_txframeend().read() != 0 { + trace!("Txframend hit, should be finished trasmitting"); + return Poll::Ready(Ok(())); + } + + if r.events_error().read() != 0 { + trace!("Got error?"); + let errs = r.errorstatus().read(); + r.errorstatus().write(|w| w.0 = 0xFFFF_FFFF); + trace!("errors: {:08x}", errs.0); + r.events_error().write_value(0); + return Poll::Ready(Err(Error::RxError)); + } + + r.inten().write(|w| { + w.set_txframeend(true); + w.set_error(true); + w.set_fieldlost(true); + }); + + Poll::Pending + }) + .await + } + + /// Receive an ISO14443-3 frame from the reader. + /// + /// After calling this, you must send back a response with [`transmit`](Self::transmit), + /// and only once. Higher-layer protocols usually define timeouts, so calling this + /// too late can cause things to fail. + pub async fn receive(&mut self, buf: &mut [u8]) -> Result { + let r = pac::NFCT; + + r.rxd().frameconfig().write(|w| { + w.set_crcmoderx(true); + w.set_parity(true); + w.set_sof(true); + }); + + //Setup DMA + r.packetptr().write_value(self.rx_buf.as_mut_ptr() as u32); + r.maxlen().write(|w| w.0 = self.rx_buf.len() as _); + + // Reset and enable the end event + r.events_rxframeend().write_value(0); + r.events_rxerror().write_value(0); + + // Start enablerxdata only after configs are finished writing + compiler_fence(Ordering::SeqCst); + r.tasks_enablerxdata().write_value(1); + + poll_fn(move |cx| { + trace!("polling rx"); + let r = pac::NFCT; + WAKER.register(cx.waker()); + + if r.events_fieldlost().read() != 0 { + return Poll::Ready(Err(Error::Deactivated)); + } + + if r.events_rxerror().read() != 0 { + trace!("RXerror got in recv frame, should be back in idle state"); + r.events_rxerror().write_value(0); + let errs = r.framestatus().rx().read(); + r.framestatus().rx().write(|w| w.0 = 0xFFFF_FFFF); + trace!("errors: {:08x}", errs.0); + return Poll::Ready(Err(Error::RxError)); + } + + if r.events_rxframeend().read() != 0 { + trace!("RX Frameend got in recv frame, should have data"); + r.events_rxframeend().write_value(0); + return Poll::Ready(Ok(())); + } + + r.inten().write(|w| { + w.set_rxframeend(true); + w.set_rxerror(true); + w.set_fieldlost(true); + }); + + Poll::Pending + }) + .await?; + + let n = r.rxd().amount().read().rxdatabytes() as usize - 2; + buf[..n].copy_from_slice(&self.rx_buf[..n]); + Ok(n) + } +} + +/// Wake the system if there if an NFC field close to the antenna +pub fn wake_on_nfc_sense() { + NFCT.tasks_sense().write_value(0x01); +} diff --git a/embassy-nrf/src/nvmc.rs b/embassy-nrf/src/nvmc.rs index 9b17e7da0..c46af0b34 100644 --- a/embassy-nrf/src/nvmc.rs +++ b/embassy-nrf/src/nvmc.rs @@ -2,13 +2,13 @@ use core::{ptr, slice}; -use embassy_hal_internal::{into_ref, PeripheralRef}; use embedded_storage::nor_flash::{ ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, }; +use crate::pac::nvmc::vals; use crate::peripherals::NVMC; -use crate::{pac, Peripheral}; +use crate::{pac, Peri}; #[cfg(not(feature = "_nrf5340-net"))] /// Erase size of NVMC flash in bytes. @@ -41,23 +41,22 @@ impl NorFlashError for Error { /// Non-Volatile Memory Controller (NVMC) that implements the `embedded-storage` traits. pub struct Nvmc<'d> { - _p: PeripheralRef<'d, NVMC>, + _p: Peri<'d, NVMC>, } impl<'d> Nvmc<'d> { /// Create Nvmc driver. - pub fn new(_p: impl Peripheral

+ 'd) -> Self { - into_ref!(_p); + pub fn new(_p: Peri<'d, NVMC>) -> Self { Self { _p } } - fn regs() -> &'static pac::nvmc::RegisterBlock { - unsafe { &*pac::NVMC::ptr() } + fn regs() -> pac::nvmc::Nvmc { + pac::NVMC } fn wait_ready(&mut self) { let p = Self::regs(); - while p.ready.read().ready().is_busy() {} + while !p.ready().read().ready() {} } #[cfg(not(any(feature = "_nrf91", feature = "_nrf5340")))] @@ -68,12 +67,12 @@ impl<'d> Nvmc<'d> { #[cfg(any(feature = "_nrf91", feature = "_nrf5340"))] fn wait_ready_write(&mut self) { let p = Self::regs(); - while p.readynext.read().readynext().is_busy() {} + while !p.readynext().read().readynext() {} } #[cfg(not(any(feature = "_nrf91", feature = "_nrf5340")))] fn erase_page(&mut self, page_addr: u32) { - Self::regs().erasepage().write(|w| unsafe { w.bits(page_addr) }); + Self::regs().erasepage().write_value(page_addr); } #[cfg(any(feature = "_nrf91", feature = "_nrf5340"))] @@ -86,23 +85,23 @@ impl<'d> Nvmc<'d> { fn enable_erase(&self) { #[cfg(not(feature = "_ns"))] - Self::regs().config.write(|w| w.wen().een()); + Self::regs().config().write(|w| w.set_wen(vals::Wen::EEN)); #[cfg(feature = "_ns")] - Self::regs().configns.write(|w| w.wen().een()); + Self::regs().configns().write(|w| w.set_wen(vals::ConfignsWen::EEN)); } fn enable_read(&self) { #[cfg(not(feature = "_ns"))] - Self::regs().config.write(|w| w.wen().ren()); + Self::regs().config().write(|w| w.set_wen(vals::Wen::REN)); #[cfg(feature = "_ns")] - Self::regs().configns.write(|w| w.wen().ren()); + Self::regs().configns().write(|w| w.set_wen(vals::ConfignsWen::REN)); } fn enable_write(&self) { #[cfg(not(feature = "_ns"))] - Self::regs().config.write(|w| w.wen().wen()); + Self::regs().config().write(|w| w.set_wen(vals::Wen::WEN)); #[cfg(feature = "_ns")] - Self::regs().configns.write(|w| w.wen().wen()); + Self::regs().configns().write(|w| w.set_wen(vals::ConfignsWen::WEN)); } } diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index 5160fe3c4..c2a4ba65f 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -8,23 +8,24 @@ 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 embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::I7F1; use crate::chip::EASY_DMA_SIZE; -use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin}; +use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin, DISCONNECTED}; use crate::interrupt::typelevel::Interrupt; -use crate::pac::pdm::mode::{EDGE_A, OPERATION_A}; -pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::pdm::vals; +pub use crate::pac::pdm::vals::Freq as Frequency; #[cfg(any( feature = "nrf52840", feature = "nrf52833", feature = "_nrf5340-app", feature = "_nrf91", ))] -pub use crate::pac::pdm::ratio::RATIO_A as Ratio; -use crate::{interrupt, Peripheral}; +pub use crate::pac::pdm::vals::Ratio; +use crate::{interrupt, pac}; /// Interrupt handler pub struct InterruptHandler { @@ -35,16 +36,16 @@ impl interrupt::typelevel::Handler for InterruptHandl unsafe fn on_interrupt() { let r = T::regs(); - if r.events_end.read().bits() != 0 { - r.intenclr.write(|w| w.end().clear()); + if r.events_end().read() != 0 { + r.intenclr().write(|w| w.set_end(true)); } - if r.events_started.read().bits() != 0 { - r.intenclr.write(|w| w.started().clear()); + if r.events_started().read() != 0 { + r.intenclr().write(|w| w.set_started(true)); } - if r.events_stopped.read().bits() != 0 { - r.intenclr.write(|w| w.stopped().clear()); + if r.events_stopped().read() != 0 { + r.intenclr().write(|w| w.set_stopped(true)); } T::state().waker.wake(); @@ -53,7 +54,7 @@ impl interrupt::typelevel::Handler for InterruptHandl /// PDM microphone interface pub struct Pdm<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, + _peri: Peri<'d, T>, } /// PDM error @@ -88,71 +89,60 @@ pub enum SamplerState { impl<'d, T: Instance> Pdm<'d, T> { /// Create PDM driver pub fn new( - pdm: impl Peripheral

+ 'd, + pdm: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - clk: impl Peripheral

+ 'd, - din: impl Peripheral

+ 'd, + clk: Peri<'d, impl GpioPin>, + din: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(pdm, clk, din); - Self::new_inner(pdm, clk.map_into(), din.map_into(), config) + Self::new_inner(pdm, clk.into(), din.into(), config) } - fn new_inner( - pdm: PeripheralRef<'d, T>, - clk: PeripheralRef<'d, AnyPin>, - din: PeripheralRef<'d, AnyPin>, - config: Config, - ) -> Self { - into_ref!(pdm); - + fn new_inner(pdm: Peri<'d, T>, clk: Peri<'d, AnyPin>, din: Peri<'d, AnyPin>, config: Config) -> Self { let r = T::regs(); // setup gpio pins - din.conf().write(|w| w.input().set_bit()); - r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) }); + din.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().din().write_value(din.psel_bits()); clk.set_low(); - clk.conf().write(|w| w.dir().output()); - r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) }); + clk.conf().write(|w| w.set_dir(gpiovals::Dir::OUTPUT)); + r.psel().clk().write_value(clk.psel_bits()); // configure - r.pdmclkctrl.write(|w| w.freq().variant(config.frequency)); + r.pdmclkctrl().write(|w| w.set_freq(config.frequency)); #[cfg(any( feature = "nrf52840", feature = "nrf52833", feature = "_nrf5340-app", feature = "_nrf91", ))] - r.ratio.write(|w| w.ratio().variant(config.ratio)); - r.mode.write(|w| { - w.operation().variant(config.operation_mode.into()); - w.edge().variant(config.edge.into()); - w + r.ratio().write(|w| w.set_ratio(config.ratio)); + r.mode().write(|w| { + w.set_operation(config.operation_mode.into()); + w.set_edge(config.edge.into()); }); Self::_set_gain(r, config.gain_left, config.gain_right); // Disable all events interrupts - r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); + r.intenclr().write(|w| w.0 = 0x003F_FFFF); // IRQ T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - r.enable.write(|w| w.enable().set_bit()); + r.enable().write(|w| w.set_enable(true)); Self { _peri: pdm } } - fn _set_gain(r: &crate::pac::pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) { - 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) } + fn _set_gain(r: pac::pdm::Pdm, gain_left: I7F1, gain_right: I7F1) { + let gain_to_bits = |gain: I7F1| -> vals::Gain { + let gain: i8 = gain.saturating_add(I7F1::from_bits(0x28)).to_bits().clamp(0, 0x50); + vals::Gain::from_bits(gain as u8) }; - 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) }); + r.gainl().write(|w| w.set_gainl(gain_to_bits(gain_left))); + r.gainr().write(|w| w.set_gainr(gain_to_bits(gain_right))); } /// Adjust the gain of the PDM microphone on the fly @@ -166,21 +156,17 @@ impl<'d, T: Instance> Pdm<'d, T> { let r = T::regs(); // start dummy sampling because microphone needs some setup time - r.sample - .ptr - .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); - r.sample - .maxcnt - .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(DUMMY_BUFFER.len() as _)); - r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.tasks_start().write_value(1); } /// 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(); + r.tasks_stop().write_value(1); + r.events_started().write_value(0); } /// Sample data into the given buffer @@ -194,41 +180,33 @@ impl<'d, T: Instance> Pdm<'d, T> { let r = T::regs(); - if r.events_started.read().bits() == 0 { + if r.events_started().read() == 0 { return Err(Error::NotRunning); } let drop = OnDrop::new(move || { - r.intenclr.write(|w| w.end().clear()); - r.events_stopped.reset(); + r.intenclr().write(|w| w.set_end(true)); + r.events_stopped().write_value(0); // reset to dummy buffer - r.sample - .ptr - .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); - r.sample - .maxcnt - .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(DUMMY_BUFFER.len() as _)); - while r.events_stopped.read().bits() == 0 {} + while r.events_stopped().read() == 0 {} }); // setup user buffer let ptr = buffer.as_ptr(); let len = buffer.len(); - r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) }); - r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) }); + r.sample().ptr().write_value(ptr as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(len as _)); // wait till the current sample is finished and the user buffer sample is started Self::wait_for_sample().await; // reset the buffer back to the dummy buffer - r.sample - .ptr - .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); - r.sample - .maxcnt - .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + r.sample().ptr().write_value(DUMMY_BUFFER.as_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(DUMMY_BUFFER.len() as _)); // wait till the user buffer is sampled Self::wait_for_sample().await; @@ -241,14 +219,14 @@ impl<'d, T: Instance> Pdm<'d, T> { async fn wait_for_sample() { let r = T::regs(); - r.events_end.reset(); - r.intenset.write(|w| w.end().set()); + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); compiler_fence(Ordering::SeqCst); poll_fn(|cx| { T::state().waker.register(cx.waker()); - if r.events_end.read().bits() != 0 { + if r.events_end().read() != 0 { return Poll::Ready(()); } Poll::Pending @@ -279,40 +257,37 @@ impl<'d, T: Instance> Pdm<'d, T> { { let r = T::regs(); - if r.events_started.read().bits() != 0 { + if r.events_started().read() != 0 { return Err(Error::AlreadyRunning); } - r.sample - .ptr - .write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) }); - r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); + r.sample().ptr().write_value(bufs[0].as_mut_ptr() as u32); + r.sample().maxcnt().write(|w| w.set_buffsize(N as _)); // Reset and enable the events - r.events_end.reset(); - r.events_started.reset(); - r.events_stopped.reset(); - r.intenset.write(|w| { - w.end().set(); - w.started().set(); - w.stopped().set(); - w + r.events_end().write_value(0); + r.events_started().write_value(0); + r.events_stopped().write_value(0); + r.intenset().write(|w| { + w.set_end(true); + w.set_started(true); + w.set_stopped(true); }); // Don't reorder the start event before the previous writes. Hopefully self // wouldn't happen anyway compiler_fence(Ordering::SeqCst); - r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.tasks_start().write_value(1); let mut current_buffer = 0; let mut done = false; let drop = OnDrop::new(|| { - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.tasks_stop().write_value(1); // N.B. It would be better if this were async, but Drop only support sync code - while r.events_stopped.read().bits() != 0 {} + while r.events_stopped().read() != 0 {} }); // Wait for events and complete when the sampler indicates it has had enough @@ -321,11 +296,11 @@ impl<'d, T: Instance> Pdm<'d, T> { T::state().waker.register(cx.waker()); - if r.events_end.read().bits() != 0 { + if r.events_end().read() != 0 { compiler_fence(Ordering::SeqCst); - r.events_end.reset(); - r.intenset.write(|w| w.end().set()); + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); if !done { // Discard the last buffer after the user requested a stop @@ -333,23 +308,21 @@ impl<'d, T: Instance> Pdm<'d, T> { let next_buffer = 1 - current_buffer; current_buffer = next_buffer; } else { - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.tasks_stop().write_value(1); done = true; }; }; } - if r.events_started.read().bits() != 0 { - r.events_started.reset(); - r.intenset.write(|w| w.started().set()); + if r.events_started().read() != 0 { + r.events_started().write_value(0); + r.intenset().write(|w| w.set_started(true)); let next_buffer = 1 - current_buffer; - r.sample - .ptr - .write(|w| unsafe { w.sampleptr().bits(bufs[next_buffer].as_mut_ptr() as u32) }); + r.sample().ptr().write_value(bufs[next_buffer].as_mut_ptr() as u32); } - if r.events_stopped.read().bits() != 0 { + if r.events_stopped().read() != 0 { return Poll::Ready(()); } @@ -411,11 +384,11 @@ pub enum OperationMode { Stereo, } -impl From for OPERATION_A { +impl From for vals::Operation { fn from(mode: OperationMode) -> Self { match mode { - OperationMode::Mono => OPERATION_A::MONO, - OperationMode::Stereo => OPERATION_A::STEREO, + OperationMode::Mono => vals::Operation::MONO, + OperationMode::Stereo => vals::Operation::STEREO, } } } @@ -429,11 +402,11 @@ pub enum Edge { LeftFalling, } -impl From for EDGE_A { +impl From for vals::Edge { fn from(edge: Edge) -> Self { match edge { - Edge::LeftRising => EDGE_A::LEFT_RISING, - Edge::LeftFalling => EDGE_A::LEFT_FALLING, + Edge::LeftRising => vals::Edge::LEFT_RISING, + Edge::LeftFalling => vals::Edge::LEFT_FALLING, } } } @@ -442,12 +415,12 @@ impl<'d, T: Instance> Drop for Pdm<'d, T> { fn drop(&mut self) { let r = T::regs(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.tasks_stop().write_value(1); - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(false)); - r.psel.din.reset(); - r.psel.clk.reset(); + r.psel().din().write_value(DISCONNECTED); + r.psel().clk().write_value(DISCONNECTED); } } @@ -465,13 +438,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static crate::pac::pdm::RegisterBlock; + fn regs() -> crate::pac::pdm::Pdm; fn state() -> &'static State; } /// PDM peripheral instance #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral type Interrupt: interrupt::typelevel::Interrupt; } @@ -479,8 +452,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_pdm { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::pdm::SealedInstance for peripherals::$type { - fn regs() -> &'static crate::pac::pdm::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> crate::pac::pdm::Pdm { + pac::$pac_type } fn state() -> &'static crate::pdm::State { static STATE: crate::pdm::State = crate::pdm::State::new(); diff --git a/embassy-nrf/src/power.rs b/embassy-nrf/src/power.rs new file mode 100644 index 000000000..f93bf8f49 --- /dev/null +++ b/embassy-nrf/src/power.rs @@ -0,0 +1,14 @@ +//! Power + +#[cfg(feature = "nrf52840")] +use crate::chip::pac::POWER; +#[cfg(any(feature = "nrf9160-s", feature = "nrf9160-ns"))] +use crate::chip::pac::REGULATORS; + +/// Puts the MCU into "System Off" mode with minimal power usage +pub fn set_system_off() { + #[cfg(feature = "nrf52840")] + POWER.systemoff().write(|w| w.set_systemoff(true)); + #[cfg(any(feature = "nrf9160-s", feature = "nrf9160-ns"))] + REGULATORS.systemoff().write(|w| w.set_systemoff(true)); +} diff --git a/embassy-nrf/src/ppi/dppi.rs b/embassy-nrf/src/ppi/dppi.rs index 0bc7f821e..686f66987 100644 --- a/embassy-nrf/src/ppi/dppi.rs +++ b/embassy-nrf/src/ppi/dppi.rs @@ -1,25 +1,23 @@ -use embassy_hal_internal::into_ref; - use super::{Channel, ConfigurableChannel, Event, Ppi, Task}; -use crate::{pac, Peripheral}; +use crate::{pac, Peri}; const DPPI_ENABLE_BIT: u32 = 0x8000_0000; const DPPI_CHANNEL_MASK: u32 = 0x0000_00FF; -pub(crate) fn regs() -> &'static pac::dppic::RegisterBlock { - unsafe { &*pac::DPPIC::ptr() } +pub(crate) fn regs() -> pac::dppic::Dppic { + pac::DPPIC } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { /// Configure PPI channel to trigger `task` on `event`. - pub fn new_one_to_one(ch: impl Peripheral

+ 'd, event: Event<'d>, task: Task<'d>) -> Self { + pub fn new_one_to_one(ch: Peri<'d, C>, event: Event<'d>, task: Task<'d>) -> Self { Ppi::new_many_to_many(ch, [event], [task]) } } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { /// Configure PPI channel to trigger both `task1` and `task2` on `event`. - pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { + pub fn new_one_to_two(ch: Peri<'d, C>, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { Ppi::new_many_to_many(ch, [event], [task1, task2]) } } @@ -28,13 +26,7 @@ impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usi Ppi<'d, C, EVENT_COUNT, TASK_COUNT> { /// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires. - pub fn new_many_to_many( - ch: impl Peripheral

+ 'd, - events: [Event<'d>; EVENT_COUNT], - tasks: [Task<'d>; TASK_COUNT], - ) -> Self { - into_ref!(ch); - + pub fn new_many_to_many(ch: Peri<'d, C>, events: [Event<'d>; EVENT_COUNT], tasks: [Task<'d>; TASK_COUNT]) -> Self { let val = DPPI_ENABLE_BIT | (ch.number() as u32 & DPPI_CHANNEL_MASK); for task in tasks { if unsafe { task.subscribe_reg().read_volatile() } != 0 { @@ -57,13 +49,13 @@ impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, /// Enables the channel. pub fn enable(&mut self) { let n = self.ch.number(); - regs().chenset.write(|w| unsafe { w.bits(1 << n) }); + regs().chenset().write(|w| w.0 = 1 << n); } /// Disables the channel. pub fn disable(&mut self) { let n = self.ch.number(); - regs().chenclr.write(|w| unsafe { w.bits(1 << n) }); + regs().chenclr().write(|w| w.0 = 1 << n); } } diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs index 13f7dcc83..531777205 100644 --- a/embassy-nrf/src/ppi/mod.rs +++ b/embassy-nrf/src/ppi/mod.rs @@ -18,9 +18,10 @@ use core::marker::PhantomData; use core::ptr::NonNull; -use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; -use crate::{peripherals, Peripheral}; +use crate::pac::common::{Reg, RW, W}; +use crate::peripherals; #[cfg_attr(feature = "_dppi", path = "dppi.rs")] #[cfg_attr(feature = "_ppi", path = "ppi.rs")] @@ -29,7 +30,7 @@ pub(crate) use _version::*; /// PPI channel driver. pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> { - ch: PeripheralRef<'d, C>, + ch: Peri<'d, C>, #[cfg(feature = "_dppi")] events: [Event<'d>; EVENT_COUNT], #[cfg(feature = "_dppi")] @@ -38,19 +39,17 @@ pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize /// PPI channel group driver. pub struct PpiGroup<'d, G: Group> { - g: PeripheralRef<'d, G>, + g: Peri<'d, G>, } impl<'d, G: Group> PpiGroup<'d, G> { /// Create a new PPI group driver. /// /// The group is initialized as containing no channels. - pub fn new(g: impl Peripheral

+ 'd) -> Self { - into_ref!(g); - + pub fn new(g: Peri<'d, G>) -> Self { let r = regs(); let n = g.number(); - r.chg[n].write(|w| unsafe { w.bits(0) }); + r.chg(n).write(|_| ()); Self { g } } @@ -65,7 +64,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { let r = regs(); let ng = self.g.number(); let nc = ch.ch.number(); - r.chg[ng].modify(|r, w| unsafe { w.bits(r.bits() | 1 << nc) }); + r.chg(ng).modify(|w| w.set_ch(nc, true)); } /// Remove a PPI channel from this group. @@ -78,19 +77,19 @@ impl<'d, G: Group> PpiGroup<'d, G> { let r = regs(); let ng = self.g.number(); let nc = ch.ch.number(); - r.chg[ng].modify(|r, w| unsafe { w.bits(r.bits() & !(1 << nc)) }); + r.chg(ng).modify(|w| w.set_ch(nc, false)); } /// Enable all the channels in this group. pub fn enable_all(&mut self) { let n = self.g.number(); - regs().tasks_chg[n].en.write(|w| unsafe { w.bits(1) }); + regs().tasks_chg(n).en().write_value(1); } /// Disable all the channels in this group. pub fn disable_all(&mut self) { let n = self.g.number(); - regs().tasks_chg[n].dis.write(|w| unsafe { w.bits(1) }); + regs().tasks_chg(n).dis().write_value(1); } /// Get a reference to the "enable all" task. @@ -98,7 +97,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { /// When triggered, it will enable all the channels in this group. pub fn task_enable_all(&self) -> Task<'d> { let n = self.g.number(); - Task::from_reg(®s().tasks_chg[n].en) + Task::from_reg(regs().tasks_chg(n).en()) } /// Get a reference to the "disable all" task. @@ -106,7 +105,7 @@ impl<'d, G: Group> PpiGroup<'d, G> { /// When triggered, it will disable all the channels in this group. pub fn task_disable_all(&self) -> Task<'d> { let n = self.g.number(); - Task::from_reg(®s().tasks_chg[n].dis) + Task::from_reg(regs().tasks_chg(n).dis()) } } @@ -114,7 +113,7 @@ impl<'d, G: Group> Drop for PpiGroup<'d, G> { fn drop(&mut self) { let r = regs(); let n = self.g.number(); - r.chg[n].write(|w| unsafe { w.bits(0) }); + r.chg(n).write(|_| ()); } } @@ -143,11 +142,8 @@ impl<'d> Task<'d> { unsafe { self.0.as_ptr().write_volatile(1) }; } - pub(crate) fn from_reg(reg: &T) -> Self { - Self( - unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) }, - PhantomData, - ) + pub(crate) fn from_reg(reg: Reg) -> Self { + Self(unsafe { NonNull::new_unchecked(reg.as_ptr()) }, PhantomData) } /// Address of subscription register for this task. @@ -178,11 +174,8 @@ impl<'d> Event<'d> { Self(ptr, PhantomData) } - pub(crate) fn from_reg(reg: &'d T) -> Self { - Self( - unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) }, - PhantomData, - ) + pub(crate) fn from_reg(reg: Reg) -> Self { + Self(unsafe { NonNull::new_unchecked(reg.as_ptr()) }, PhantomData) } /// Describes whether this Event is currently in a triggered state. @@ -215,34 +208,22 @@ pub(crate) trait SealedGroup {} /// Interface for PPI channels. #[allow(private_bounds)] -pub trait Channel: SealedChannel + Peripheral

+ Sized + 'static { +pub trait Channel: SealedChannel + PeripheralType + Sized + 'static { /// Returns the number of the channel fn number(&self) -> usize; } /// Interface for PPI channels that can be configured. -pub trait ConfigurableChannel: Channel + Into { - /// Convert into a type erased configurable channel. - fn degrade(self) -> AnyConfigurableChannel; -} +pub trait ConfigurableChannel: Channel + Into {} /// Interface for PPI channels that cannot be configured. -pub trait StaticChannel: Channel + Into { - /// Convert into a type erased static channel. - fn degrade(self) -> AnyStaticChannel; -} +pub trait StaticChannel: Channel + Into {} /// Interface for a group of PPI channels. #[allow(private_bounds)] -pub trait Group: SealedGroup + Peripheral

+ Into + Sized + 'static { +pub trait Group: SealedGroup + PeripheralType + Into + Sized + 'static { /// Returns the number of the group. fn number(&self) -> usize; - /// Convert into a type erased group. - fn degrade(self) -> AnyGroup { - AnyGroup { - number: self.number() as u8, - } - } } // ====================== @@ -260,11 +241,7 @@ impl Channel for AnyStaticChannel { self.number as usize } } -impl StaticChannel for AnyStaticChannel { - fn degrade(self) -> AnyStaticChannel { - self - } -} +impl StaticChannel for AnyStaticChannel {} /// The any configurable channel can represent any configurable channel at runtime. /// This can be used to have fewer generic parameters in some places. @@ -278,13 +255,9 @@ impl Channel for AnyConfigurableChannel { self.number as usize } } -impl ConfigurableChannel for AnyConfigurableChannel { - fn degrade(self) -> AnyConfigurableChannel { - self - } -} +impl ConfigurableChannel for AnyConfigurableChannel {} -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf51"))] macro_rules! impl_ppi_channel { ($type:ident, $number:expr) => { impl crate::ppi::SealedChannel for peripherals::$type {} @@ -296,35 +269,23 @@ macro_rules! impl_ppi_channel { }; ($type:ident, $number:expr => static) => { impl_ppi_channel!($type, $number); - impl crate::ppi::StaticChannel for peripherals::$type { - fn degrade(self) -> crate::ppi::AnyStaticChannel { - use crate::ppi::Channel; - crate::ppi::AnyStaticChannel { - number: self.number() as u8, - } - } - } - + impl crate::ppi::StaticChannel for peripherals::$type {} impl From for crate::ppi::AnyStaticChannel { fn from(val: peripherals::$type) -> Self { - crate::ppi::StaticChannel::degrade(val) + Self { + number: crate::ppi::Channel::number(&val) as u8, + } } } }; ($type:ident, $number:expr => configurable) => { impl_ppi_channel!($type, $number); - impl crate::ppi::ConfigurableChannel for peripherals::$type { - fn degrade(self) -> crate::ppi::AnyConfigurableChannel { - use crate::ppi::Channel; - crate::ppi::AnyConfigurableChannel { - number: self.number() as u8, - } - } - } - + impl crate::ppi::ConfigurableChannel for peripherals::$type {} impl From for crate::ppi::AnyConfigurableChannel { fn from(val: peripherals::$type) -> Self { - crate::ppi::ConfigurableChannel::degrade(val) + Self { + number: crate::ppi::Channel::number(&val) as u8, + } } } }; @@ -356,7 +317,9 @@ macro_rules! impl_group { impl From for crate::ppi::AnyGroup { fn from(val: peripherals::$type) -> Self { - crate::ppi::Group::degrade(val) + Self { + number: crate::ppi::Group::number(&val) as u8, + } } } }; @@ -366,7 +329,7 @@ impl_group!(PPI_GROUP0, 0); impl_group!(PPI_GROUP1, 1); impl_group!(PPI_GROUP2, 2); impl_group!(PPI_GROUP3, 3); -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf51"))] impl_group!(PPI_GROUP4, 4); -#[cfg(not(feature = "nrf51"))] +#[cfg(not(feature = "_nrf51"))] impl_group!(PPI_GROUP5, 5); diff --git a/embassy-nrf/src/ppi/ppi.rs b/embassy-nrf/src/ppi/ppi.rs index 8ff52ece3..e04dacbc0 100644 --- a/embassy-nrf/src/ppi/ppi.rs +++ b/embassy-nrf/src/ppi/ppi.rs @@ -1,7 +1,5 @@ -use embassy_hal_internal::into_ref; - use super::{Channel, ConfigurableChannel, Event, Ppi, Task}; -use crate::{pac, Peripheral}; +use crate::{pac, Peri}; impl<'d> Task<'d> { fn reg_val(&self) -> u32 { @@ -14,19 +12,17 @@ impl<'d> Event<'d> { } } -pub(crate) fn regs() -> &'static pac::ppi::RegisterBlock { - unsafe { &*pac::PPI::ptr() } +pub(crate) fn regs() -> pac::ppi::Ppi { + pac::PPI } -#[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task +#[cfg(not(feature = "_nrf51"))] // Not for nrf51 because of the fork task 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

+ 'd, task: Task) -> Self { - into_ref!(ch); - + pub fn new_zero_to_one(ch: Peri<'d, C>, task: Task) -> Self { let r = regs(); let n = ch.number(); - r.fork[n].tep.write(|w| unsafe { w.bits(task.reg_val()) }); + r.fork(n).tep().write_value(task.reg_val()); Self { ch } } @@ -34,29 +30,25 @@ impl<'d, C: super::StaticChannel> Ppi<'d, C, 0, 1> { impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { /// Configure PPI channel to trigger `task` on `event`. - pub fn new_one_to_one(ch: impl Peripheral

+ 'd, event: Event<'d>, task: Task<'d>) -> Self { - into_ref!(ch); - + pub fn new_one_to_one(ch: Peri<'d, C>, event: Event<'d>, task: Task<'d>) -> Self { let r = regs(); let n = ch.number(); - r.ch[n].eep.write(|w| unsafe { w.bits(event.reg_val()) }); - r.ch[n].tep.write(|w| unsafe { w.bits(task.reg_val()) }); + r.ch(n).eep().write_value(event.reg_val()); + r.ch(n).tep().write_value(task.reg_val()); Self { ch } } } -#[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task +#[cfg(not(feature = "_nrf51"))] // Not for nrf51 because of the fork task impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { /// Configure PPI channel to trigger both `task1` and `task2` on `event`. - pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { - into_ref!(ch); - + pub fn new_one_to_two(ch: Peri<'d, C>, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { let r = regs(); let n = ch.number(); - r.ch[n].eep.write(|w| unsafe { w.bits(event.reg_val()) }); - r.ch[n].tep.write(|w| unsafe { w.bits(task1.reg_val()) }); - r.fork[n].tep.write(|w| unsafe { w.bits(task2.reg_val()) }); + r.ch(n).eep().write_value(event.reg_val()); + r.ch(n).tep().write_value(task1.reg_val()); + r.fork(n).tep().write_value(task2.reg_val()); Self { ch } } @@ -66,13 +58,13 @@ impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, /// Enables the channel. pub fn enable(&mut self) { let n = self.ch.number(); - regs().chenset.write(|w| unsafe { w.bits(1 << n) }); + regs().chenset().write(|w| w.set_ch(n, true)); } /// Disables the channel. pub fn disable(&mut self) { let n = self.ch.number(); - regs().chenclr.write(|w| unsafe { w.bits(1 << n) }); + regs().chenclr().write(|w| w.set_ch(n, true)); } } @@ -82,9 +74,9 @@ impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Drop for let r = regs(); 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) }); + r.ch(n).eep().write_value(0); + r.ch(n).tep().write_value(0); + #[cfg(not(feature = "_nrf51"))] + r.fork(n).tep().write_value(0); } } diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 8e8f166d7..a2e153e26 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -4,32 +4,34 @@ use core::sync::atomic::{compiler_fence, Ordering}; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; -use crate::gpio::{convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _}; +use crate::gpio::{convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _, DISCONNECTED}; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::pwm::vals; use crate::ppi::{Event, Task}; use crate::util::slice_in_ram_or; -use crate::{interrupt, pac, Peripheral}; +use crate::{interrupt, pac}; /// SimplePwm is the traditional pwm interface you're probably used to, allowing /// to simply set a duty cycle across up to four channels. pub struct SimplePwm<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, + _peri: Peri<'d, T>, duty: [u16; 4], - ch0: Option>, - ch1: Option>, - ch2: Option>, - ch3: Option>, + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, } /// SequencePwm allows you to offload the updating of a sequence of duty cycles /// to up to four channels, as well as repeat that sequence n times. pub struct SequencePwm<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, - ch0: Option>, - ch1: Option>, - ch2: Option>, - ch3: Option>, + _peri: Peri<'d, T>, + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, } /// PWM error @@ -52,128 +54,124 @@ pub const PWM_CLK_HZ: u32 = 16_000_000; impl<'d, T: Instance> SequencePwm<'d, T> { /// Create a new 1-channel PWM #[allow(unused_unsafe)] - pub fn new_1ch( - pwm: impl Peripheral

+ 'd, - ch0: impl Peripheral

+ 'd, - config: Config, - ) -> Result { - into_ref!(ch0); - Self::new_inner(pwm, Some(ch0.map_into()), None, None, None, config) + pub fn new_1ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, config: Config) -> Result { + Self::new_inner(pwm, Some(ch0.into()), None, None, None, config) } /// Create a new 2-channel PWM #[allow(unused_unsafe)] pub fn new_2ch( - pwm: impl Peripheral

+ 'd, - ch0: impl Peripheral

+ 'd, - ch1: impl Peripheral

+ 'd, + pwm: Peri<'d, T>, + ch0: Peri<'d, impl GpioPin>, + ch1: Peri<'d, impl GpioPin>, config: Config, ) -> Result { - into_ref!(ch0, ch1); - Self::new_inner(pwm, Some(ch0.map_into()), Some(ch1.map_into()), None, None, config) + Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), None, None, config) } /// Create a new 3-channel PWM #[allow(unused_unsafe)] pub fn new_3ch( - pwm: impl Peripheral

+ 'd, - ch0: impl Peripheral

+ 'd, - ch1: impl Peripheral

+ 'd, - ch2: impl Peripheral

+ 'd, + pwm: Peri<'d, T>, + ch0: Peri<'d, impl GpioPin>, + ch1: Peri<'d, impl GpioPin>, + ch2: Peri<'d, impl GpioPin>, config: Config, ) -> Result { - into_ref!(ch0, ch1, ch2); - Self::new_inner( - pwm, - Some(ch0.map_into()), - Some(ch1.map_into()), - Some(ch2.map_into()), - None, - config, - ) + Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), None, config) } /// Create a new 4-channel PWM #[allow(unused_unsafe)] pub fn new_4ch( - pwm: impl Peripheral

+ 'd, - ch0: impl Peripheral

+ 'd, - ch1: impl Peripheral

+ 'd, - ch2: impl Peripheral

+ 'd, - ch3: impl Peripheral

+ 'd, + pwm: Peri<'d, T>, + ch0: Peri<'d, impl GpioPin>, + ch1: Peri<'d, impl GpioPin>, + ch2: Peri<'d, impl GpioPin>, + ch3: Peri<'d, impl GpioPin>, config: Config, ) -> Result { - into_ref!(ch0, ch1, ch2, ch3); Self::new_inner( pwm, - Some(ch0.map_into()), - Some(ch1.map_into()), - Some(ch2.map_into()), - Some(ch3.map_into()), + Some(ch0.into()), + Some(ch1.into()), + Some(ch2.into()), + Some(ch3.into()), config, ) } fn new_inner( - _pwm: impl Peripheral

+ 'd, - ch0: Option>, - ch1: Option>, - ch2: Option>, - ch3: Option>, + _pwm: Peri<'d, T>, + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, config: Config, ) -> Result { - into_ref!(_pwm); - let r = T::regs(); if let Some(pin) = &ch0 { pin.set_low(); - pin.conf() - .write(|w| w.dir().output().drive().variant(convert_drive(config.ch0_drive))); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch0_drive); + }); } if let Some(pin) = &ch1 { pin.set_low(); - pin.conf() - .write(|w| w.dir().output().drive().variant(convert_drive(config.ch1_drive))); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch1_drive); + }); } if let Some(pin) = &ch2 { pin.set_low(); - pin.conf() - .write(|w| w.dir().output().drive().variant(convert_drive(config.ch2_drive))); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch2_drive); + }); } if let Some(pin) = &ch3 { pin.set_low(); - pin.conf() - .write(|w| w.dir().output().drive().variant(convert_drive(config.ch3_drive))); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + convert_drive(w, config.ch3_drive); + }); } - r.psel.out[0].write(|w| unsafe { w.bits(ch0.psel_bits()) }); - r.psel.out[1].write(|w| unsafe { w.bits(ch1.psel_bits()) }); - r.psel.out[2].write(|w| unsafe { w.bits(ch2.psel_bits()) }); - r.psel.out[3].write(|w| unsafe { w.bits(ch3.psel_bits()) }); + r.psel().out(0).write_value(ch0.psel_bits()); + r.psel().out(1).write_value(ch1.psel_bits()); + r.psel().out(2).write_value(ch2.psel_bits()); + r.psel().out(3).write_value(ch3.psel_bits()); // Disable all interrupts - r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - r.shorts.reset(); - r.events_stopped.reset(); - r.events_loopsdone.reset(); - r.events_seqend[0].reset(); - r.events_seqend[1].reset(); - r.events_pwmperiodend.reset(); - r.events_seqstarted[0].reset(); - r.events_seqstarted[1].reset(); + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + r.shorts().write(|_| ()); + r.events_stopped().write_value(0); + r.events_loopsdone().write_value(0); + r.events_seqend(0).write_value(0); + r.events_seqend(1).write_value(0); + r.events_pwmperiodend().write_value(0); + r.events_seqstarted(0).write_value(0); + r.events_seqstarted(1).write_value(0); - r.decoder.write(|w| { - w.load().bits(config.sequence_load as u8); - w.mode().refresh_count() + r.decoder().write(|w| { + w.set_load(vals::Load::from_bits(config.sequence_load as u8)); + w.set_mode(vals::Mode::REFRESH_COUNT); }); - r.mode.write(|w| match config.counter_mode { - CounterMode::UpAndDown => w.updown().up_and_down(), - CounterMode::Up => w.updown().up(), + r.mode().write(|w| match config.counter_mode { + CounterMode::UpAndDown => w.set_updown(vals::Updown::UP_AND_DOWN), + CounterMode::Up => w.set_updown(vals::Updown::UP), }); - r.prescaler.write(|w| w.prescaler().bits(config.prescaler as u8)); - r.countertop.write(|w| unsafe { w.countertop().bits(config.max_duty) }); + r.prescaler() + .write(|w| w.set_prescaler(vals::Prescaler::from_bits(config.prescaler as u8))); + r.countertop().write(|w| w.set_countertop(config.max_duty)); Ok(Self { _peri: _pwm, @@ -189,7 +187,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub fn event_stopped(&self) -> Event<'d> { let r = T::regs(); - Event::from_reg(&r.events_stopped) + Event::from_reg(r.events_stopped()) } /// Returns reference to `LoopsDone` event endpoint for PPI. @@ -197,7 +195,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub fn event_loops_done(&self) -> Event<'d> { let r = T::regs(); - Event::from_reg(&r.events_loopsdone) + Event::from_reg(r.events_loopsdone()) } /// Returns reference to `PwmPeriodEnd` event endpoint for PPI. @@ -205,7 +203,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub fn event_pwm_period_end(&self) -> Event<'d> { let r = T::regs(); - Event::from_reg(&r.events_pwmperiodend) + Event::from_reg(r.events_pwmperiodend()) } /// Returns reference to `Seq0 End` event endpoint for PPI. @@ -213,7 +211,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub fn event_seq_end(&self) -> Event<'d> { let r = T::regs(); - Event::from_reg(&r.events_seqend[0]) + Event::from_reg(r.events_seqend(0)) } /// Returns reference to `Seq1 End` event endpoint for PPI. @@ -221,7 +219,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub fn event_seq1_end(&self) -> Event<'d> { let r = T::regs(); - Event::from_reg(&r.events_seqend[1]) + Event::from_reg(r.events_seqend(1)) } /// Returns reference to `Seq0 Started` event endpoint for PPI. @@ -229,7 +227,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub fn event_seq0_started(&self) -> Event<'d> { let r = T::regs(); - Event::from_reg(&r.events_seqstarted[0]) + Event::from_reg(r.events_seqstarted(0)) } /// Returns reference to `Seq1 Started` event endpoint for PPI. @@ -237,7 +235,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub fn event_seq1_started(&self) -> Event<'d> { let r = T::regs(); - Event::from_reg(&r.events_seqstarted[1]) + Event::from_reg(r.events_seqstarted(1)) } /// Returns reference to `Seq0 Start` task endpoint for PPI. @@ -248,7 +246,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub unsafe fn task_start_seq0(&self) -> Task<'d> { let r = T::regs(); - Task::from_reg(&r.tasks_seqstart[0]) + Task::from_reg(r.tasks_seqstart(0)) } /// Returns reference to `Seq1 Started` task endpoint for PPI. @@ -259,7 +257,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub unsafe fn task_start_seq1(&self) -> Task<'d> { let r = T::regs(); - Task::from_reg(&r.tasks_seqstart[1]) + Task::from_reg(r.tasks_seqstart(1)) } /// Returns reference to `NextStep` task endpoint for PPI. @@ -270,7 +268,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub unsafe fn task_next_step(&self) -> Task<'d> { let r = T::regs(); - Task::from_reg(&r.tasks_nextstep) + Task::from_reg(r.tasks_nextstep()) } /// Returns reference to `Stop` task endpoint for PPI. @@ -281,7 +279,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { pub unsafe fn task_stop(&self) -> Task<'d> { let r = T::regs(); - Task::from_reg(&r.tasks_stop) + Task::from_reg(r.tasks_stop()) } } @@ -291,23 +289,23 @@ impl<'a, T: Instance> Drop for SequencePwm<'a, T> { if let Some(pin) = &self.ch0 { pin.set_low(); - pin.conf().reset(); - r.psel.out[0].reset(); + pin.conf().write(|_| ()); + r.psel().out(0).write_value(DISCONNECTED); } if let Some(pin) = &self.ch1 { pin.set_low(); - pin.conf().reset(); - r.psel.out[1].reset(); + pin.conf().write(|_| ()); + r.psel().out(1).write_value(DISCONNECTED); } if let Some(pin) = &self.ch2 { pin.set_low(); - pin.conf().reset(); - r.psel.out[2].reset(); + pin.conf().write(|_| ()); + r.psel().out(2).write_value(DISCONNECTED); } if let Some(pin) = &self.ch3 { pin.set_low(); - pin.conf().reset(); - r.psel.out[3].reset(); + pin.conf().write(|_| ()); + r.psel().out(3).write_value(DISCONNECTED); } } } @@ -463,21 +461,17 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { let r = T::regs(); - r.seq0.refresh.write(|w| unsafe { w.bits(sequence0.config.refresh) }); - r.seq0.enddelay.write(|w| unsafe { w.bits(sequence0.config.end_delay) }); - r.seq0.ptr.write(|w| unsafe { w.bits(sequence0.words.as_ptr() as u32) }); - r.seq0.cnt.write(|w| unsafe { w.bits(sequence0.words.len() as u32) }); + r.seq(0).refresh().write(|w| w.0 = sequence0.config.refresh); + r.seq(0).enddelay().write(|w| w.0 = sequence0.config.end_delay); + r.seq(0).ptr().write_value(sequence0.words.as_ptr() as u32); + r.seq(0).cnt().write(|w| w.0 = sequence0.words.len() as u32); - r.seq1.refresh.write(|w| unsafe { w.bits(alt_sequence.config.refresh) }); - r.seq1 - .enddelay - .write(|w| unsafe { w.bits(alt_sequence.config.end_delay) }); - r.seq1 - .ptr - .write(|w| unsafe { w.bits(alt_sequence.words.as_ptr() as u32) }); - r.seq1.cnt.write(|w| unsafe { w.bits(alt_sequence.words.len() as u32) }); + r.seq(1).refresh().write(|w| w.0 = alt_sequence.config.refresh); + r.seq(1).enddelay().write(|w| w.0 = alt_sequence.config.end_delay); + r.seq(1).ptr().write_value(alt_sequence.words.as_ptr() as u32); + r.seq(1).cnt().write(|w| w.0 = alt_sequence.words.len() as u32); - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(true)); // defensive before seqstart compiler_fence(Ordering::SeqCst); @@ -486,18 +480,17 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { match times { // just the one time, no loop count - SequenceMode::Loop(n) => { - r.loop_.write(|w| unsafe { w.cnt().bits(n) }); + SequenceMode::Loop(_) => { + r.loop_().write(|w| w.set_cnt(vals::LoopCnt::DISABLED)); } // to play infinitely, repeat the sequence one time, then have loops done self trigger seq0 again SequenceMode::Infinite => { - r.loop_.write(|w| unsafe { w.cnt().bits(0x1) }); - r.shorts.write(|w| w.loopsdone_seqstart0().enabled()); + r.loop_().write(|w| w.set_cnt(vals::LoopCnt::from_bits(1))); + r.shorts().write(|w| w.set_loopsdone_seqstart0(true)); } } - // tasks_seqstart() doesn't exist in all svds so write its bit instead - r.tasks_seqstart[seqstart_index].write(|w| unsafe { w.bits(0x01) }); + r.tasks_seqstart(seqstart_index).write_value(1); Ok(()) } @@ -509,14 +502,12 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { pub fn stop(&self) { let r = T::regs(); - r.shorts.reset(); + r.shorts().write(|_| ()); compiler_fence(Ordering::SeqCst); - // tasks_stop() doesn't exist in all svds so write its bit instead - r.tasks_stop.write(|w| unsafe { w.bits(0x01) }); - - r.enable.write(|w| w.enable().disabled()); + r.tasks_stop().write_value(1); + r.enable().write(|w| w.set_enable(false)); } } @@ -602,99 +593,68 @@ pub enum CounterMode { impl<'d, T: Instance> SimplePwm<'d, T> { /// Create a new 1-channel PWM #[allow(unused_unsafe)] - pub fn new_1ch(pwm: impl Peripheral

+ 'd, ch0: impl Peripheral

+ 'd) -> Self { - unsafe { - into_ref!(ch0); - Self::new_inner(pwm, Some(ch0.map_into()), None, None, None) - } + pub fn new_1ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>) -> Self { + unsafe { Self::new_inner(pwm, Some(ch0.into()), None, None, None) } } /// Create a new 2-channel PWM #[allow(unused_unsafe)] - pub fn new_2ch( - pwm: impl Peripheral

+ 'd, - ch0: impl Peripheral

+ 'd, - ch1: impl Peripheral

+ 'd, - ) -> Self { - into_ref!(ch0, ch1); - Self::new_inner(pwm, Some(ch0.map_into()), Some(ch1.map_into()), None, None) + pub fn new_2ch(pwm: Peri<'d, T>, ch0: Peri<'d, impl GpioPin>, ch1: Peri<'d, impl GpioPin>) -> Self { + Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), None, None) } /// Create a new 3-channel PWM #[allow(unused_unsafe)] pub fn new_3ch( - pwm: impl Peripheral

+ 'd, - ch0: impl Peripheral

+ 'd, - ch1: impl Peripheral

+ 'd, - ch2: impl Peripheral

+ 'd, + pwm: Peri<'d, T>, + ch0: Peri<'d, impl GpioPin>, + ch1: Peri<'d, impl GpioPin>, + ch2: Peri<'d, impl GpioPin>, ) -> Self { - unsafe { - into_ref!(ch0, ch1, ch2); - Self::new_inner( - pwm, - Some(ch0.map_into()), - Some(ch1.map_into()), - Some(ch2.map_into()), - None, - ) - } + unsafe { Self::new_inner(pwm, Some(ch0.into()), Some(ch1.into()), Some(ch2.into()), None) } } /// Create a new 4-channel PWM #[allow(unused_unsafe)] pub fn new_4ch( - pwm: impl Peripheral

+ 'd, - ch0: impl Peripheral

+ 'd, - ch1: impl Peripheral

+ 'd, - ch2: impl Peripheral

+ 'd, - ch3: impl Peripheral

+ 'd, + pwm: Peri<'d, T>, + ch0: Peri<'d, impl GpioPin>, + ch1: Peri<'d, impl GpioPin>, + ch2: Peri<'d, impl GpioPin>, + ch3: Peri<'d, impl GpioPin>, ) -> Self { unsafe { - into_ref!(ch0, ch1, ch2, ch3); Self::new_inner( pwm, - Some(ch0.map_into()), - Some(ch1.map_into()), - Some(ch2.map_into()), - Some(ch3.map_into()), + Some(ch0.into()), + Some(ch1.into()), + Some(ch2.into()), + Some(ch3.into()), ) } } fn new_inner( - _pwm: impl Peripheral

+ 'd, - ch0: Option>, - ch1: Option>, - ch2: Option>, - ch3: Option>, + _pwm: Peri<'d, T>, + ch0: Option>, + ch1: Option>, + ch2: Option>, + ch3: Option>, ) -> Self { - into_ref!(_pwm); - let r = T::regs(); - if let Some(pin) = &ch0 { - pin.set_low(); - pin.conf().write(|w| w.dir().output()); - } - if let Some(pin) = &ch1 { - pin.set_low(); - pin.conf().write(|w| w.dir().output()); - } - if let Some(pin) = &ch2 { - pin.set_low(); - pin.conf().write(|w| w.dir().output()); - } - if let Some(pin) = &ch3 { - pin.set_low(); - pin.conf().write(|w| w.dir().output()); - } + for (i, ch) in [&ch0, &ch1, &ch2, &ch3].into_iter().enumerate() { + if let Some(pin) = ch { + pin.set_low(); - // if NoPin provided writes disconnected (top bit 1) 0x80000000 else - // writes pin number ex 13 (0x0D) which is connected (top bit 0) - r.psel.out[0].write(|w| unsafe { w.bits(ch0.psel_bits()) }); - r.psel.out[1].write(|w| unsafe { w.bits(ch1.psel_bits()) }); - r.psel.out[2].write(|w| unsafe { w.bits(ch2.psel_bits()) }); - r.psel.out[3].write(|w| unsafe { w.bits(ch3.psel_bits()) }); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + w.set_drive(gpiovals::Drive::S0S1); + }); + } + r.psel().out(i).write_value(ch.psel_bits()); + } let pwm = Self { _peri: _pwm, @@ -706,26 +666,25 @@ impl<'d, T: Instance> SimplePwm<'d, T> { }; // Disable all interrupts - r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - r.shorts.reset(); + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); + r.shorts().write(|_| ()); // Enable - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(true)); - r.seq0.ptr.write(|w| unsafe { w.bits((pwm.duty).as_ptr() as u32) }); + r.seq(0).ptr().write_value((pwm.duty).as_ptr() as u32); + r.seq(0).cnt().write(|w| w.0 = 4); + r.seq(0).refresh().write(|w| w.0 = 0); + r.seq(0).enddelay().write(|w| w.0 = 0); - r.seq0.cnt.write(|w| unsafe { w.bits(4) }); - r.seq0.refresh.write(|w| unsafe { w.bits(0) }); - r.seq0.enddelay.write(|w| unsafe { w.bits(0) }); - - r.decoder.write(|w| { - w.load().individual(); - w.mode().refresh_count() + r.decoder().write(|w| { + w.set_load(vals::Load::INDIVIDUAL); + w.set_mode(vals::Mode::REFRESH_COUNT); }); - r.mode.write(|w| w.updown().up()); - r.prescaler.write(|w| w.prescaler().div_16()); - r.countertop.write(|w| unsafe { w.countertop().bits(1000) }); - r.loop_.write(|w| w.cnt().disabled()); + r.mode().write(|w| w.set_updown(vals::Updown::UP)); + r.prescaler().write(|w| w.set_prescaler(vals::Prescaler::DIV_16)); + r.countertop().write(|w| w.set_countertop(1000)); + r.loop_().write(|w| w.set_cnt(vals::LoopCnt::DISABLED)); pwm } @@ -734,21 +693,21 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn is_enabled(&self) -> bool { let r = T::regs(); - r.enable.read().enable().bit_is_set() + r.enable().read().enable() } /// Enables the PWM generator. #[inline(always)] pub fn enable(&self) { let r = T::regs(); - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(true)); } /// Disables the PWM generator. Does NOT clear the last duty cycle from the pin. #[inline(always)] pub fn disable(&self) { let r = T::regs(); - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(false)); } /// Returns the current duty of the channel @@ -763,33 +722,35 @@ 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.seq(0).ptr().write_value((self.duty).as_ptr() as u32); // defensive before seqstart compiler_fence(Ordering::SeqCst); - r.events_seqend[0].reset(); + r.events_seqend(0).write_value(0); // tasks_seqstart() doesn't exist in all svds so write its bit instead - r.tasks_seqstart[0].write(|w| unsafe { w.bits(1) }); + r.tasks_seqstart(0).write_value(1); // defensive wait until waveform is loaded after seqstart so set_duty // can't be called again while dma is still reading if self.is_enabled() { - while r.events_seqend[0].read().bits() == 0 {} + while r.events_seqend(0).read() == 0 {} } } /// Sets the PWM clock prescaler. #[inline(always)] pub fn set_prescaler(&self, div: Prescaler) { - T::regs().prescaler.write(|w| w.prescaler().bits(div as u8)); + T::regs() + .prescaler() + .write(|w| w.set_prescaler(vals::Prescaler::from_bits(div as u8))); } /// Gets the PWM clock prescaler. #[inline(always)] pub fn prescaler(&self) -> Prescaler { - match T::regs().prescaler.read().prescaler().bits() { + match T::regs().prescaler().read().prescaler().to_bits() { 0 => Prescaler::Div1, 1 => Prescaler::Div2, 2 => Prescaler::Div4, @@ -805,15 +766,13 @@ impl<'d, T: Instance> SimplePwm<'d, T> { /// Sets the maximum duty cycle value. #[inline(always)] pub fn set_max_duty(&self, duty: u16) { - T::regs() - .countertop - .write(|w| unsafe { w.countertop().bits(duty.min(32767u16)) }); + T::regs().countertop().write(|w| w.set_countertop(duty.min(32767u16))); } /// Returns the maximum duty cycle value. #[inline(always)] pub fn max_duty(&self) -> u16 { - T::regs().countertop.read().countertop().bits() + T::regs().countertop().read().countertop() } /// Sets the PWM output frequency. @@ -836,7 +795,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch0_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch0 { - pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|w| convert_drive(w, drive)); } } @@ -844,7 +803,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch1_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch1 { - pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|w| convert_drive(w, drive)); } } @@ -852,7 +811,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch2_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch2 { - pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|w| convert_drive(w, drive)); } } @@ -860,7 +819,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { #[inline(always)] pub fn set_ch3_drive(&self, drive: OutputDrive) { if let Some(pin) = &self.ch3 { - pin.conf().modify(|_, w| w.drive().variant(convert_drive(drive))); + pin.conf().modify(|w| convert_drive(w, drive)); } } } @@ -873,34 +832,34 @@ impl<'a, T: Instance> Drop for SimplePwm<'a, T> { if let Some(pin) = &self.ch0 { pin.set_low(); - pin.conf().reset(); - r.psel.out[0].reset(); + pin.conf().write(|_| ()); + r.psel().out(0).write_value(DISCONNECTED); } if let Some(pin) = &self.ch1 { pin.set_low(); - pin.conf().reset(); - r.psel.out[1].reset(); + pin.conf().write(|_| ()); + r.psel().out(1).write_value(DISCONNECTED); } if let Some(pin) = &self.ch2 { pin.set_low(); - pin.conf().reset(); - r.psel.out[2].reset(); + pin.conf().write(|_| ()); + r.psel().out(2).write_value(DISCONNECTED); } if let Some(pin) = &self.ch3 { pin.set_low(); - pin.conf().reset(); - r.psel.out[3].reset(); + pin.conf().write(|_| ()); + r.psel().out(3).write_value(DISCONNECTED); } } } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::pwm0::RegisterBlock; + fn regs() -> pac::pwm::Pwm; } /// PWM peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static { +pub trait Instance: SealedInstance + PeripheralType + 'static { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -908,8 +867,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static { macro_rules! impl_pwm { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::pwm::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::pwm0::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::pwm::Pwm { + pac::$pac_type } } impl crate::pwm::Instance for peripherals::$type { diff --git a/embassy-nrf/src/qdec.rs b/embassy-nrf/src/qdec.rs index 7409c9b1e..69bfab0bb 100644 --- a/embassy-nrf/src/qdec.rs +++ b/embassy-nrf/src/qdec.rs @@ -6,16 +6,18 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin as _}; use crate::interrupt::typelevel::Interrupt; -use crate::{interrupt, Peripheral}; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::qdec::vals; +use crate::{interrupt, pac}; /// Quadrature decoder driver. pub struct Qdec<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } /// QDEC config @@ -52,7 +54,7 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - T::regs().intenclr.write(|w| w.reportrdy().clear()); + T::regs().intenclr().write(|w| w.set_reportrdy(true)); T::state().waker.wake(); } } @@ -60,87 +62,90 @@ impl interrupt::typelevel::Handler for InterruptHandl impl<'d, T: Instance> Qdec<'d, T> { /// Create a new QDEC. pub fn new( - qdec: impl Peripheral

+ 'd, + qdec: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - a: impl Peripheral

+ 'd, - b: impl Peripheral

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

+ 'd, + qdec: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - a: impl Peripheral

+ 'd, - b: impl Peripheral

+ 'd, - led: impl Peripheral

+ 'd, + a: Peri<'d, impl GpioPin>, + b: Peri<'d, impl GpioPin>, + led: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(qdec, a, b, led); - Self::new_inner(qdec, a.map_into(), b.map_into(), Some(led.map_into()), config) + Self::new_inner(qdec, a.into(), b.into(), Some(led.into()), config) } fn new_inner( - p: PeripheralRef<'d, T>, - a: PeripheralRef<'d, AnyPin>, - b: PeripheralRef<'d, AnyPin>, - led: Option>, + p: Peri<'d, T>, + a: Peri<'d, AnyPin>, + b: Peri<'d, AnyPin>, + led: Option>, config: Config, ) -> Self { let r = T::regs(); // Select pins. - a.conf().write(|w| w.input().connect().pull().pullup()); - b.conf().write(|w| w.input().connect().pull().pullup()); - r.psel.a.write(|w| unsafe { w.bits(a.psel_bits()) }); - r.psel.b.write(|w| unsafe { w.bits(b.psel_bits()) }); + a.conf().write(|w| { + w.set_input(gpiovals::Input::CONNECT); + w.set_pull(gpiovals::Pull::PULLUP); + }); + b.conf().write(|w| { + w.set_input(gpiovals::Input::CONNECT); + w.set_pull(gpiovals::Pull::PULLUP); + }); + r.psel().a().write_value(a.psel_bits()); + r.psel().b().write_value(b.psel_bits()); if let Some(led_pin) = &led { - led_pin.conf().write(|w| w.dir().output()); - r.psel.led.write(|w| unsafe { w.bits(led_pin.psel_bits()) }); + led_pin.conf().write(|w| w.set_dir(gpiovals::Dir::OUTPUT)); + r.psel().led().write_value(led_pin.psel_bits()); } // Enables/disable input debounce filters - r.dbfen.write(|w| match config.debounce { - true => w.dbfen().enabled(), - false => w.dbfen().disabled(), + r.dbfen().write(|w| match config.debounce { + true => w.set_dbfen(true), + false => w.set_dbfen(false), }); // Set LED output pin polarity - r.ledpol.write(|w| match config.led_polarity { - LedPolarity::ActiveHigh => w.ledpol().active_high(), - LedPolarity::ActiveLow => w.ledpol().active_low(), + r.ledpol().write(|w| match config.led_polarity { + LedPolarity::ActiveHigh => w.set_ledpol(vals::Ledpol::ACTIVE_HIGH), + LedPolarity::ActiveLow => w.set_ledpol(vals::Ledpol::ACTIVE_LOW), }); // Set time period the LED is switched ON prior to sampling (0..511 us). - r.ledpre - .write(|w| unsafe { w.ledpre().bits(config.led_pre_usecs.min(511)) }); + r.ledpre().write(|w| w.set_ledpre(config.led_pre_usecs.min(511))); // Set sample period - r.sampleper.write(|w| match config.period { - SamplePeriod::_128us => w.sampleper()._128us(), - SamplePeriod::_256us => w.sampleper()._256us(), - SamplePeriod::_512us => w.sampleper()._512us(), - SamplePeriod::_1024us => w.sampleper()._1024us(), - SamplePeriod::_2048us => w.sampleper()._2048us(), - SamplePeriod::_4096us => w.sampleper()._4096us(), - SamplePeriod::_8192us => w.sampleper()._8192us(), - SamplePeriod::_16384us => w.sampleper()._16384us(), - SamplePeriod::_32ms => w.sampleper()._32ms(), - SamplePeriod::_65ms => w.sampleper()._65ms(), - SamplePeriod::_131ms => w.sampleper()._131ms(), + r.sampleper().write(|w| match config.period { + SamplePeriod::_128us => w.set_sampleper(vals::Sampleper::_128US), + SamplePeriod::_256us => w.set_sampleper(vals::Sampleper::_256US), + SamplePeriod::_512us => w.set_sampleper(vals::Sampleper::_512US), + SamplePeriod::_1024us => w.set_sampleper(vals::Sampleper::_1024US), + SamplePeriod::_2048us => w.set_sampleper(vals::Sampleper::_2048US), + SamplePeriod::_4096us => w.set_sampleper(vals::Sampleper::_4096US), + SamplePeriod::_8192us => w.set_sampleper(vals::Sampleper::_8192US), + SamplePeriod::_16384us => w.set_sampleper(vals::Sampleper::_16384US), + SamplePeriod::_32ms => w.set_sampleper(vals::Sampleper::_32MS), + SamplePeriod::_65ms => w.set_sampleper(vals::Sampleper::_65MS), + SamplePeriod::_131ms => w.set_sampleper(vals::Sampleper::_131MS), }); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; // Enable peripheral - r.enable.write(|w| w.enable().set_bit()); + r.enable().write(|w| w.set_enable(true)); // Start sampling - unsafe { r.tasks_start.write(|w| w.bits(1)) }; + r.tasks_start().write_value(1); Self { _p: p } } @@ -169,16 +174,16 @@ impl<'d, T: Instance> Qdec<'d, T> { /// ``` pub async fn read(&mut self) -> i16 { let t = T::regs(); - t.intenset.write(|w| w.reportrdy().set()); - unsafe { t.tasks_readclracc.write(|w| w.bits(1)) }; + t.intenset().write(|w| w.set_reportrdy(true)); + t.tasks_readclracc().write_value(1); poll_fn(|cx| { T::state().waker.register(cx.waker()); - if t.events_reportrdy.read().bits() == 0 { + if t.events_reportrdy().read() == 0 { Poll::Pending } else { - t.events_reportrdy.reset(); - let acc = t.accread.read().bits(); + t.events_reportrdy().write_value(0); + let acc = t.accread().read(); Poll::Ready(acc as i16) } }) @@ -259,13 +264,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static crate::pac::qdec::RegisterBlock; + fn regs() -> pac::qdec::Qdec; fn state() -> &'static State; } /// qdec peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -273,8 +278,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_qdec { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::qdec::SealedInstance for peripherals::$type { - fn regs() -> &'static crate::pac::qdec::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::qdec::Qdec { + pac::$pac_type } fn state() -> &'static crate::qdec::State { static STATE: crate::qdec::State = crate::qdec::State::new(); diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs index d40096edc..e6e829f6e 100755 --- a/embassy-nrf/src/qspi.rs +++ b/embassy-nrf/src/qspi.rs @@ -2,23 +2,24 @@ #![macro_use] -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::ptr; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use crate::gpio::{self, Pin as GpioPin}; use crate::interrupt::typelevel::Interrupt; -pub use crate::pac::qspi::ifconfig0::{ - ADDRMODE_A as AddressMode, PPSIZE_A as WritePageSize, READOC_A as ReadOpcode, WRITEOC_A as WriteOpcode, +use crate::pac::gpio::vals as gpiovals; +use crate::pac::qspi::vals; +pub use crate::pac::qspi::vals::{ + Addrmode as AddressMode, Ppsize as WritePageSize, Readoc as ReadOpcode, Spimode as SpiMode, Writeoc as WriteOpcode, }; -pub use crate::pac::qspi::ifconfig1::SPIMODE_A as SpiMode; -use crate::{interrupt, Peripheral}; +use crate::{interrupt, pac}; /// Deep power-down config. pub struct DeepPowerDownConfig { @@ -129,16 +130,16 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); - if r.events_ready.read().bits() != 0 { + if r.events_ready().read() != 0 { s.waker.wake(); - r.intenclr.write(|w| w.ready().clear()); + r.intenclr().write(|w| w.set_ready(true)); } } } /// QSPI flash driver. pub struct Qspi<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, + _peri: Peri<'d, T>, dpm_enabled: bool, capacity: u32, } @@ -146,31 +147,28 @@ pub struct Qspi<'d, T: Instance> { impl<'d, T: Instance> Qspi<'d, T> { /// Create a new QSPI driver. pub fn new( - qspi: impl Peripheral

+ 'd, + qspi: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - sck: impl Peripheral

+ 'd, - csn: impl Peripheral

+ 'd, - io0: impl Peripheral

+ 'd, - io1: impl Peripheral

+ 'd, - io2: impl Peripheral

+ 'd, - io3: impl Peripheral

+ 'd, + sck: Peri<'d, impl GpioPin>, + csn: Peri<'d, impl GpioPin>, + io0: Peri<'d, impl GpioPin>, + io1: Peri<'d, impl GpioPin>, + io2: Peri<'d, impl GpioPin>, + io3: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(qspi, sck, csn, io0, io1, io2, io3); - let r = T::regs(); macro_rules! config_pin { ($pin:ident) => { $pin.set_high(); $pin.conf().write(|w| { - w.dir().output(); - w.drive().h0h1(); + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_drive(gpiovals::Drive::H0H1); #[cfg(all(feature = "_nrf5340", feature = "_s"))] - w.mcusel().peripheral(); - w + w.set_mcusel(gpiovals::Mcusel::PERIPHERAL); }); - r.psel.$pin.write(|w| unsafe { w.bits($pin.psel_bits()) }); + r.psel().$pin().write_value($pin.psel_bits()); }; } @@ -181,46 +179,39 @@ impl<'d, T: Instance> Qspi<'d, T> { config_pin!(io2); config_pin!(io3); - r.ifconfig0.write(|w| { - w.addrmode().variant(config.address_mode); - w.dpmenable().bit(config.deep_power_down.is_some()); - w.ppsize().variant(config.write_page_size); - w.readoc().variant(config.read_opcode); - w.writeoc().variant(config.write_opcode); - w + r.ifconfig0().write(|w| { + w.set_addrmode(config.address_mode); + w.set_dpmenable(config.deep_power_down.is_some()); + w.set_ppsize(config.write_page_size); + w.set_readoc(config.read_opcode); + w.set_writeoc(config.write_opcode); }); if let Some(dpd) = &config.deep_power_down { - r.dpmdur.write(|w| unsafe { - w.enter().bits(dpd.enter_time); - w.exit().bits(dpd.exit_time); - w + r.dpmdur().write(|w| { + w.set_enter(dpd.enter_time); + w.set_exit(dpd.exit_time); }) } - r.ifconfig1.write(|w| unsafe { - w.sckdelay().bits(config.sck_delay); - w.dpmen().exit(); - w.spimode().variant(config.spi_mode); - w.sckfreq().bits(config.frequency as u8); - w + r.ifconfig1().write(|w| { + w.set_sckdelay(config.sck_delay); + w.set_dpmen(false); + w.set_spimode(config.spi_mode); + w.set_sckfreq(config.frequency as u8); }); - r.iftiming.write(|w| unsafe { - w.rxdelay().bits(config.rx_delay & 0b111); - w + r.iftiming().write(|w| { + w.set_rxdelay(config.rx_delay & 0b111); }); - r.xipoffset.write(|w| unsafe { - w.xipoffset().bits(config.xip_offset); - w - }); + r.xipoffset().write_value(config.xip_offset); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; // Enable it - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(true)); let res = Self { _peri: qspi, @@ -228,10 +219,10 @@ impl<'d, T: Instance> Qspi<'d, T> { capacity: config.capacity, }; - r.events_ready.reset(); - r.intenset.write(|w| w.ready().set()); + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); - r.tasks_activate.write(|w| w.tasks_activate().bit(true)); + r.tasks_activate().write_value(1); Self::blocking_wait_ready(); @@ -284,22 +275,21 @@ impl<'d, T: Instance> Qspi<'d, T> { } let r = T::regs(); - r.cinstrdat0.write(|w| unsafe { w.bits(dat0) }); - r.cinstrdat1.write(|w| unsafe { w.bits(dat1) }); + r.cinstrdat0().write(|w| w.0 = dat0); + r.cinstrdat1().write(|w| w.0 = dat1); - r.events_ready.reset(); - r.intenset.write(|w| w.ready().set()); + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); - r.cinstrconf.write(|w| { - let w = unsafe { w.opcode().bits(opcode) }; - let w = unsafe { w.length().bits(len + 1) }; - let w = w.lio2().bit(true); - let w = w.lio3().bit(true); - let w = w.wipwait().bit(true); - let w = w.wren().bit(true); - let w = w.lfen().bit(false); - let w = w.lfstop().bit(false); - w + r.cinstrconf().write(|w| { + w.set_opcode(opcode); + w.set_length(vals::Length::from_bits(len + 1)); + w.set_lio2(true); + w.set_lio3(true); + w.set_wipwait(true); + w.set_wren(true); + w.set_lfen(false); + w.set_lfstop(false); }); Ok(()) } @@ -307,8 +297,8 @@ impl<'d, T: Instance> Qspi<'d, T> { fn custom_instruction_finish(&mut self, resp: &mut [u8]) -> Result<(), Error> { let r = T::regs(); - let dat0 = r.cinstrdat0.read().bits(); - let dat1 = r.cinstrdat1.read().bits(); + let dat0 = r.cinstrdat0().read().0; + let dat1 = r.cinstrdat1().read().0; for i in 0..4 { if i < resp.len() { resp[i] = (dat0 >> (i * 8)) as u8; @@ -322,23 +312,22 @@ impl<'d, T: Instance> Qspi<'d, T> { Ok(()) } - async fn wait_ready(&mut self) { + fn wait_ready(&mut self) -> impl Future { poll_fn(move |cx| { let r = T::regs(); let s = T::state(); s.waker.register(cx.waker()); - if r.events_ready.read().bits() != 0 { + if r.events_ready().read() != 0 { return Poll::Ready(()); } Poll::Pending }) - .await } fn blocking_wait_ready() { loop { let r = T::regs(); - if r.events_ready.read().bits() != 0 { + if r.events_ready().read() != 0 { break; } } @@ -352,13 +341,13 @@ impl<'d, T: Instance> Qspi<'d, T> { let r = T::regs(); - r.read.src.write(|w| unsafe { w.src().bits(address) }); - r.read.dst.write(|w| unsafe { w.dst().bits(data.as_ptr() as u32) }); - r.read.cnt.write(|w| unsafe { w.cnt().bits(data.len() as u32) }); + r.read().src().write_value(address); + r.read().dst().write_value(data.as_ptr() as u32); + r.read().cnt().write(|w| w.set_cnt(data.len() as u32)); - r.events_ready.reset(); - r.intenset.write(|w| w.ready().set()); - r.tasks_readstart.write(|w| w.tasks_readstart().bit(true)); + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + r.tasks_readstart().write_value(1); Ok(()) } @@ -370,13 +359,13 @@ impl<'d, T: Instance> Qspi<'d, T> { assert_eq!(address % 4, 0); let r = T::regs(); - r.write.src.write(|w| unsafe { w.src().bits(data.as_ptr() as u32) }); - r.write.dst.write(|w| unsafe { w.dst().bits(address) }); - r.write.cnt.write(|w| unsafe { w.cnt().bits(data.len() as u32) }); + r.write().src().write_value(data.as_ptr() as u32); + r.write().dst().write_value(address); + r.write().cnt().write(|w| w.set_cnt(data.len() as u32)); - r.events_ready.reset(); - r.intenset.write(|w| w.ready().set()); - r.tasks_writestart.write(|w| w.tasks_writestart().bit(true)); + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + r.tasks_writestart().write_value(1); Ok(()) } @@ -386,12 +375,12 @@ impl<'d, T: Instance> Qspi<'d, T> { assert_eq!(address % 4096, 0); let r = T::regs(); - r.erase.ptr.write(|w| unsafe { w.ptr().bits(address) }); - r.erase.len.write(|w| w.len()._4kb()); + r.erase().ptr().write_value(address); + r.erase().len().write(|w| w.set_len(vals::Len::_4KB)); - r.events_ready.reset(); - r.intenset.write(|w| w.ready().set()); - r.tasks_erasestart.write(|w| w.tasks_erasestart().bit(true)); + r.events_ready().write_value(0); + r.intenset().write(|w| w.set_ready(true)); + r.tasks_erasestart().write_value(1); Ok(()) } @@ -538,12 +527,12 @@ impl<'d, T: Instance> Drop for Qspi<'d, T> { if self.dpm_enabled { trace!("qspi: doing deep powerdown..."); - r.ifconfig1.modify(|_, w| w.dpmen().enter()); + r.ifconfig1().modify(|w| w.set_dpmen(true)); // Wait for DPM enter. // Unfortunately we must spin. There's no way to do this interrupt-driven. // The READY event does NOT fire on DPM enter (but it does fire on DPM exit :shrug:) - while r.status.read().dpm().is_disabled() {} + while !r.status().read().dpm() {} // Wait MORE for DPM enter. // I have absolutely no idea why, but the wait above is not enough :'( @@ -552,23 +541,23 @@ impl<'d, T: Instance> Drop for Qspi<'d, T> { } // it seems events_ready is not generated in response to deactivate. nrfx doesn't wait for it. - r.tasks_deactivate.write(|w| w.tasks_deactivate().set_bit()); + r.tasks_deactivate().write_value(1); - // Workaround https://infocenter.nordicsemi.com/topic/errata_nRF52840_Rev1/ERR/nRF52840/Rev1/latest/anomaly_840_122.html?cp=4_0_1_2_1_7 + // Workaround https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_122.html // Note that the doc has 2 register writes, but the first one is really the write to tasks_deactivate, // so we only do the second one here. unsafe { ptr::write_volatile(0x40029054 as *mut u32, 1) } - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(false)); // Note: we do NOT deconfigure CSN here. If DPM is in use and we disconnect CSN, // leaving it floating, the flash chip might read it as zero which would cause it to // spuriously exit DPM. - gpio::deconfigure_pin(r.psel.sck.read().bits()); - gpio::deconfigure_pin(r.psel.io0.read().bits()); - gpio::deconfigure_pin(r.psel.io1.read().bits()); - gpio::deconfigure_pin(r.psel.io2.read().bits()); - gpio::deconfigure_pin(r.psel.io3.read().bits()); + gpio::deconfigure_pin(r.psel().sck().read()); + gpio::deconfigure_pin(r.psel().io0().read()); + gpio::deconfigure_pin(r.psel().io1().read()); + gpio::deconfigure_pin(r.psel().io2().read()); + gpio::deconfigure_pin(r.psel().io3().read()); trace!("qspi: dropped"); } @@ -667,13 +656,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static crate::pac::qspi::RegisterBlock; + fn regs() -> pac::qspi::Qspi; fn state() -> &'static State; } /// QSPI peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -681,8 +670,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_qspi { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::qspi::SealedInstance for peripherals::$type { - fn regs() -> &'static crate::pac::qspi::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::qspi::Qspi { + pac::$pac_type } fn state() -> &'static crate::qspi::State { static STATE: crate::qspi::State = crate::qspi::State::new(); diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs deleted file mode 100644 index 4f0b0641f..000000000 --- a/embassy-nrf/src/radio/ble.rs +++ /dev/null @@ -1,412 +0,0 @@ -//! 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

+ 'd, - _irq: impl interrupt::typelevel::Binding> + '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()) { - 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.tasks_stop.write(|w| unsafe { w.bits(1) }); - - r.events_end.reset(); - - 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(); - - // 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_end.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 index 298f8a574..7f4f8f462 100644 --- a/embassy-nrf/src/radio/ieee802154.rs +++ b/embassy-nrf/src/radio/ieee802154.rs @@ -4,12 +4,13 @@ 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 super::{Error, Instance, InterruptHandler, TxPower}; use crate::interrupt::typelevel::Interrupt; use crate::interrupt::{self}; -use crate::Peripheral; +use crate::pac::radio::vals; +pub use crate::pac::radio::vals::State as RadioState; +use crate::Peri; /// Default (IEEE compliant) Start of Frame Delimiter pub const DEFAULT_SFD: u8 = 0xA7; @@ -32,73 +33,60 @@ pub enum Cca { /// IEEE 802.15.4 radio driver. pub struct Radio<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'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

+ 'd, + radio: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + '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()); + r.power().write(|w| w.set_power(false)); + r.power().write(|w| w.set_power(true)); // Enable 802.15.4 mode - r.mode.write(|w| w.mode().ieee802154_250kbit()); + r.mode().write(|w| w.set_mode(vals::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() - }); - } + r.crccnf().write(|w| { + w.set_len(vals::Len::TWO); + w.set_skipaddr(vals::Skipaddr::IEEE802154); + }); + // Configure CRC polynomial and init + r.crcpoly().write(|w| w.set_crcpoly(0x0001_1021)); + r.crcinit().write(|w| w.set_crcinit(0)); + r.pcnf0().write(|w| { + // 8-bit on air length + w.set_lflen(8); + // Zero bytes S0 field length + w.set_s0len(false); + // Zero bytes S1 field length + w.set_s1len(0); + // Do not include S1 field in RAM if S1 length > 0 + w.set_s1incl(vals::S1incl::AUTOMATIC); + // Zero code Indicator length + w.set_cilen(0); + // 32-bit zero preamble + w.set_plen(vals::Plen::_32BIT_ZERO); + // Include CRC in length + w.set_crcinc(vals::Crcinc::INCLUDE); + }); + r.pcnf1().write(|w| { + // Maximum packet length + w.set_maxlen(Packet::MAX_PSDU_LEN); + // Zero static length + w.set_statlen(0); + // Zero base address length + w.set_balen(0); + // Little-endian + w.set_endian(vals::Endian::LITTLE); + // Disable packet whitening + w.set_whiteen(false); + }); // Enable NVIC interrupt T::Interrupt::unpend(); @@ -125,8 +113,10 @@ impl<'d, T: Instance> Radio<'d, T> { } let frequency_offset = (channel - 10) * 5; self.needs_enable = true; - r.frequency - .write(|w| unsafe { w.frequency().bits(frequency_offset).map().default() }); + r.frequency().write(|w| { + w.set_frequency(frequency_offset); + w.set_map(vals::Map::DEFAULT); + }); } /// Changes the Clear Channel Assessment method @@ -134,12 +124,14 @@ impl<'d, T: Instance> Radio<'d, T> { let r = T::regs(); self.needs_enable = true; match cca { - Cca::CarrierSense => r.ccactrl.write(|w| w.ccamode().carrier_mode()), + Cca::CarrierSense => r.ccactrl().write(|w| w.set_ccamode(vals::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) }); + r.ccactrl().write(|w| { + w.set_ccamode(vals::Ccamode::ED_MODE); + w.set_ccaedthres(ed_threshold); + }); } } } @@ -147,13 +139,13 @@ impl<'d, T: Instance> Radio<'d, T> { /// 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) }); + r.sfd().write(|w| w.set_sfd(sfd)); } /// Clear interrupts pub fn clear_all_interrupts(&mut self) { let r = T::regs(); - r.intenclr.write(|w| unsafe { w.bits(0xffff_ffff) }); + r.intenclr().write(|w| w.0 = 0xffff_ffff); } /// Changes the radio transmission power @@ -163,43 +155,43 @@ impl<'d, T: Instance> Radio<'d, T> { let tx_power: TxPower = match power { #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] - 8 => TxPower::POS8D_BM, + 8 => TxPower::POS8_DBM, #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] - 7 => TxPower::POS7D_BM, + 7 => TxPower::POS7_DBM, #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] - 6 => TxPower::POS6D_BM, + 6 => TxPower::POS6_DBM, #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] - 5 => TxPower::POS5D_BM, + 5 => TxPower::POS5_DBM, #[cfg(not(feature = "_nrf5340-net"))] - 4 => TxPower::POS4D_BM, + 4 => TxPower::POS4_DBM, #[cfg(not(feature = "_nrf5340-net"))] - 3 => TxPower::POS3D_BM, + 3 => TxPower::POS3_DBM, #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] - 2 => TxPower::POS2D_BM, - 0 => TxPower::_0D_BM, + 2 => TxPower::POS2_DBM, + 0 => TxPower::_0_DBM, #[cfg(feature = "_nrf5340-net")] - -1 => TxPower::NEG1D_BM, + -1 => TxPower::NEG1_DBM, #[cfg(feature = "_nrf5340-net")] - -2 => TxPower::NEG2D_BM, + -2 => TxPower::NEG2_DBM, #[cfg(feature = "_nrf5340-net")] - -3 => TxPower::NEG3D_BM, - -4 => TxPower::NEG4D_BM, + -3 => TxPower::NEG3_DBM, + -4 => TxPower::NEG4_DBM, #[cfg(feature = "_nrf5340-net")] - -5 => TxPower::NEG5D_BM, + -5 => TxPower::NEG5_DBM, #[cfg(feature = "_nrf5340-net")] - -6 => TxPower::NEG6D_BM, + -6 => TxPower::NEG6_DBM, #[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, + -7 => TxPower::NEG7_DBM, + -8 => TxPower::NEG8_DBM, + -12 => TxPower::NEG12_DBM, + -16 => TxPower::NEG16_DBM, + -20 => TxPower::NEG20_DBM, + -30 => TxPower::NEG30_DBM, + -40 => TxPower::NEG40_DBM, _ => panic!("Invalid transmission power value"), }; - r.txpower.write(|w| w.txpower().variant(tx_power)); + r.txpower().write(|w| w.set_txpower(tx_power)); } /// Waits until the radio state matches the given `state` @@ -209,7 +201,7 @@ impl<'d, T: Instance> Radio<'d, T> { /// Get the current radio state fn state(&self) -> RadioState { - state(T::regs()) + T::regs().state().read().state() } /// Moves the radio from any state to the DISABLED state @@ -221,7 +213,7 @@ impl<'d, T: Instance> Radio<'d, T> { 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()); + r.tasks_disable().write_value(1); self.wait_for_radio_state(RadioState::DISABLED); return; } @@ -232,29 +224,30 @@ impl<'d, T: Instance> Radio<'d, T> { } // 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()); + r.tasks_ccastop().write_value(1); + r.tasks_stop().write_value(1); self.wait_for_radio_state(RadioState::RX_IDLE); } RadioState::TX => { - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + r.tasks_stop().write_value(1); self.wait_for_radio_state(RadioState::TX_IDLE); } + _ => unreachable!(), } } } fn set_buffer(&mut self, buffer: &[u8]) { let r = T::regs(); - r.packetptr.write(|w| unsafe { w.bits(buffer.as_ptr() as u32) }); + r.packetptr().write_value(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 + T::regs().events_ccabusy().write_value(0); + T::regs().events_phyend().write_value(0); + // NOTE to avoid errata 204 (see rev1 v1.4) we do TX_IDLE -> DISABLED -> RXIDLE let disable = match self.state() { RadioState::DISABLED => false, RadioState::RX_IDLE => self.needs_enable, @@ -279,7 +272,7 @@ impl<'d, T: Instance> Radio<'d, T> { // 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()); + r.shorts().write(|w| w.set_rxready_start(true)); // set up RX buffer self.set_buffer(packet.buffer.as_mut()); @@ -289,19 +282,19 @@ impl<'d, T: Instance> Radio<'d, T> { match self.state() { // Re-start receiver - RadioState::RX_IDLE => r.tasks_start.write(|w| w.tasks_start().set_bit()), + RadioState::RX_IDLE => r.tasks_start().write_value(1), // Enable receiver - _ => r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()), + _ => r.tasks_rxen().write_value(1), } } /// Cancel receiving packet fn receive_cancel() { let r = T::regs(); - r.shorts.reset(); - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + r.shorts().write(|_| {}); + r.tasks_stop().write_value(1); loop { - match state(r) { + match r.state().read().state() { RadioState::DISABLED | RadioState::RX_IDLE => break, _ => (), } @@ -329,12 +322,12 @@ impl<'d, T: Instance> Radio<'d, T> { 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(); + if r.events_phyend().read() != 0 { + r.events_phyend().write_value(0); trace!("RX done poll"); return Poll::Ready(()); } else { - r.intenset.write(|w| w.phyend().set()); + r.intenset().write(|w| w.set_phyend(true)); }; Poll::Pending @@ -344,8 +337,8 @@ impl<'d, T: Instance> Radio<'d, T> { dma_end_fence(); dropper.defuse(); - let crc = r.rxcrc.read().rxcrc().bits() as u16; - if r.crcstatus.read().crcstatus().bit_is_set() { + let crc = r.rxcrc().read().rxcrc() as u16; + if r.crcstatus().read().crcstatus() == vals::Crcstatus::CRCOK { Ok(()) } else { Err(Error::CrcFailed(crc)) @@ -387,17 +380,12 @@ impl<'d, T: Instance> Radio<'d, T> { // 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() + r.shorts().write(|w| { + w.set_rxready_ccastart(true); + w.set_ccaidle_txen(true); + w.set_txready_start(true); + w.set_ccabusy_disable(true); + w.set_phyend_disable(true); }); // Set transmission buffer @@ -410,27 +398,30 @@ impl<'d, T: Instance> Radio<'d, T> { match self.state() { // Re-start receiver - RadioState::RX_IDLE => r.tasks_ccastart.write(|w| w.tasks_ccastart().set_bit()), + RadioState::RX_IDLE => r.tasks_ccastart().write_value(1), // Enable receiver - _ => r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()), + _ => r.tasks_rxen().write_value(1), } 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(); + if r.events_phyend().read() != 0 { + r.events_phyend().write_value(0); + r.events_ccabusy().write_value(0); trace!("TX done poll"); return Poll::Ready(TransmitResult::Success); - } else if r.events_ccabusy.read().events_ccabusy().bit_is_set() { - r.events_ccabusy.reset(); + } else if r.events_ccabusy().read() != 0 { + r.events_ccabusy().write_value(0); trace!("TX no CCA"); return Poll::Ready(TransmitResult::ChannelInUse); } - r.intenset.write(|w| w.phyend().set().ccabusy().set()); + r.intenset().write(|w| { + w.set_phyend(true); + w.set_ccabusy(true); + }); Poll::Pending }) diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs index 8edca1df2..608ef9024 100644 --- a/embassy-nrf/src/radio/mod.rs +++ b/embassy-nrf/src/radio/mod.rs @@ -6,7 +6,6 @@ #![macro_use] /// Bluetooth Low Energy Radio driver. -pub mod ble; #[cfg(any( feature = "nrf52811", feature = "nrf52820", @@ -19,11 +18,11 @@ pub mod ieee802154; use core::marker::PhantomData; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; -use pac::radio::state::STATE_A as RadioState; -pub use pac::radio::txpower::TXPOWER_A as TxPower; +pub use pac::radio::vals::Txpower as TxPower; -use crate::{interrupt, pac, Peripheral}; +use crate::{interrupt, pac}; /// RADIO error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -52,7 +51,7 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); // clear all interrupts - r.intenclr.write(|w| w.bits(0xffff_ffff)); + r.intenclr().write(|w| w.0 = 0xffff_ffff); s.event_waker.wake(); } } @@ -70,17 +69,18 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static crate::pac::radio::RegisterBlock; + fn regs() -> crate::pac::radio::Radio; fn state() -> &'static State; } macro_rules! impl_radio { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::radio::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::radio::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> crate::pac::radio::Radio { + pac::$pac_type } + #[allow(unused)] fn state() -> &'static crate::radio::State { static STATE: crate::radio::State = crate::radio::State::new(); &STATE @@ -94,15 +94,7 @@ macro_rules! impl_radio { /// Radio peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + '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/reset.rs b/embassy-nrf/src/reset.rs new file mode 100644 index 000000000..a33d8f31f --- /dev/null +++ b/embassy-nrf/src/reset.rs @@ -0,0 +1,82 @@ +//! Reset + +#![macro_use] + +use bitflags::bitflags; +use nrf_pac::reset::regs::Resetreas; +#[cfg(not(feature = "_nrf5340-net"))] +use nrf_pac::reset::vals::Forceoff; + +use crate::chip::pac::RESET; + +bitflags! { + #[derive(Debug, Copy, Clone, PartialEq)] + /// Bitflag representation of the `RESETREAS` register + pub struct ResetReason: u32 { + /// Reset Pin + const RESETPIN = 1; + /// Application watchdog timer 0 + const DOG0 = 1 << 1; + /// Application CTRL-AP + const CTRLAP = 1 << 2; + /// Application soft reset + const SREQ = 1 << 3; + /// Application CPU lockup + const LOCKUP = 1 << 4; + /// Wakeup from System OFF when wakeup is triggered by DETECT signal from GPIO + const OFF = 1 << 5; + /// Wakeup from System OFF when wakeup is triggered by ANADETECT signal from LPCOMP + const LPCOMP = 1 << 6; + /// Wakeup from System OFF when wakeup is triggered by entering the Debug Interface mode + const DIF = 1 << 7; + /// Network soft reset + #[cfg(feature = "_nrf5340-net")] + const LSREQ = 1 << 16; + /// Network CPU lockup + #[cfg(feature = "_nrf5340-net")] + const LLOCKUP = 1 << 17; + /// Network watchdog timer + #[cfg(feature = "_nrf5340-net")] + const LDOG = 1 << 18; + /// Force-OFF reset from application core + #[cfg(feature = "_nrf5340-net")] + const MFORCEOFF = 1 << 23; + /// Wakeup from System OFF mode due to NFC field being detected + const NFC = 1 << 24; + /// Application watchdog timer 1 + const DOG1 = 1 << 25; + /// Wakeup from System OFF mode due to VBUS rising into valid range + const VBUS = 1 << 26; + /// Network CTRL-AP + #[cfg(feature = "_nrf5340-net")] + const LCTRLAP = 1 << 27; + } +} + +/// Reads the bitflag of the reset reasons +pub fn read_reasons() -> ResetReason { + ResetReason::from_bits_retain(RESET.resetreas().read().0) +} + +/// Resets the reset reasons +pub fn clear_reasons() { + RESET.resetreas().write(|w| *w = Resetreas(ResetReason::all().bits())); +} + +/// Returns if the network core is held in reset +#[cfg(not(feature = "_nrf5340-net"))] +pub fn network_core_held() -> bool { + RESET.network().forceoff().read().forceoff() == Forceoff::HOLD +} + +/// Releases the network core from the FORCEOFF state +#[cfg(not(feature = "_nrf5340-net"))] +pub fn release_network_core() { + RESET.network().forceoff().write(|w| w.set_forceoff(Forceoff::RELEASE)); +} + +/// Holds the network core in the FORCEOFF state +#[cfg(not(feature = "_nrf5340-net"))] +pub fn hold_network_core() { + RESET.network().forceoff().write(|w| w.set_forceoff(Forceoff::HOLD)); +} diff --git a/embassy-nrf/src/rng.rs b/embassy-nrf/src/rng.rs index ff61e08f3..9d3130e6e 100644 --- a/embassy-nrf/src/rng.rs +++ b/embassy-nrf/src/rng.rs @@ -10,11 +10,12 @@ use core::task::Poll; use critical_section::{CriticalSection, Mutex}; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::WakerRegistration; use crate::interrupt::typelevel::Interrupt; -use crate::{interrupt, Peripheral}; +use crate::mode::{Async, Blocking, Mode}; +use crate::{interrupt, pac}; /// Interrupt handler. pub struct InterruptHandler { @@ -26,7 +27,7 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); // Clear the event. - r.events_valrdy.reset(); + r.events_valrdy().write_value(0); // Mutate the slice within a critical section, // so that the future isn't dropped in between us loading the pointer and actually dereferencing it. @@ -40,7 +41,7 @@ impl interrupt::typelevel::Handler for InterruptHandl // The safety contract of `Rng::new` means that the future can't have been dropped // without calling its destructor. unsafe { - *state.ptr = r.value.read().value().bits(); + *state.ptr = r.value().read().value(); state.ptr = state.ptr.add(1); } @@ -55,11 +56,31 @@ impl interrupt::typelevel::Handler for InterruptHandl /// A wrapper around an nRF RNG peripheral. /// /// It has a non-blocking API, and a blocking api through `rand`. -pub struct Rng<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, +pub struct Rng<'d, T: Instance, M: Mode> { + _peri: Peri<'d, T>, + _phantom: PhantomData, } -impl<'d, T: Instance> Rng<'d, T> { +impl<'d, T: Instance> Rng<'d, T, Blocking> { + /// Creates a new RNG driver from the `RNG` peripheral and interrupt. + /// + /// SAFETY: The future returned from `fill_bytes` must not have its lifetime end without running its destructor, + /// e.g. using `mem::forget`. + /// + /// The synchronous API is safe. + pub fn new_blocking(rng: Peri<'d, T>) -> Self { + let this = Self { + _peri: rng, + _phantom: PhantomData, + }; + + this.stop(); + + this + } +} + +impl<'d, T: Instance> Rng<'d, T, Async> { /// Creates a new RNG driver from the `RNG` peripheral and interrupt. /// /// SAFETY: The future returned from `fill_bytes` must not have its lifetime end without running its destructor, @@ -67,12 +88,13 @@ impl<'d, T: Instance> Rng<'d, T> { /// /// The synchronous API is safe. pub fn new( - rng: impl Peripheral

+ 'd, + rng: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { - into_ref!(rng); - - let this = Self { _peri: rng }; + let this = Self { + _peri: rng, + _phantom: PhantomData, + }; this.stop(); this.disable_irq(); @@ -83,30 +105,12 @@ impl<'d, T: Instance> Rng<'d, T> { this } - fn stop(&self) { - T::regs().tasks_stop.write(|w| unsafe { w.bits(1) }) - } - - fn start(&self) { - T::regs().tasks_start.write(|w| unsafe { w.bits(1) }) - } - fn enable_irq(&self) { - T::regs().intenset.write(|w| w.valrdy().set()); + T::regs().intenset().write(|w| w.set_valrdy(true)); } fn disable_irq(&self) { - T::regs().intenclr.write(|w| w.valrdy().clear()); - } - - /// Enable or disable the RNG's bias correction. - /// - /// Bias correction removes any bias towards a '1' or a '0' in the bits generated. - /// However, this makes the generation of numbers slower. - /// - /// Defaults to disabled. - pub fn set_bias_correction(&self, enable: bool) { - T::regs().config.write(|w| w.dercen().bit(enable)) + T::regs().intenclr().write(|w| w.set_valrdy(true)); } /// Fill the buffer with random bytes. @@ -155,6 +159,26 @@ impl<'d, T: Instance> Rng<'d, T> { // Trigger the teardown drop(on_drop); } +} + +impl<'d, T: Instance, M: Mode> Rng<'d, T, M> { + fn stop(&self) { + T::regs().tasks_stop().write_value(1) + } + + fn start(&self) { + T::regs().tasks_start().write_value(1) + } + + /// Enable or disable the RNG's bias correction. + /// + /// Bias correction removes any bias towards a '1' or a '0' in the bits generated. + /// However, this makes the generation of numbers slower. + /// + /// Defaults to disabled. + pub fn set_bias_correction(&self, enable: bool) { + T::regs().config().write(|w| w.set_dercen(enable)) + } /// Fill the buffer with random bytes, blocking version. pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { @@ -162,16 +186,31 @@ impl<'d, T: Instance> Rng<'d, T> { for byte in dest.iter_mut() { let regs = T::regs(); - while regs.events_valrdy.read().bits() == 0 {} - regs.events_valrdy.reset(); - *byte = regs.value.read().value().bits(); + while regs.events_valrdy().read() == 0 {} + regs.events_valrdy().write_value(0); + *byte = regs.value().read().value(); } self.stop(); } + + /// Generate a random u32 + pub fn blocking_next_u32(&mut self) -> u32 { + let mut bytes = [0; 4]; + self.blocking_fill_bytes(&mut bytes); + // We don't care about the endianness, so just use the native one. + u32::from_ne_bytes(bytes) + } + + /// Generate a random u64 + pub fn blocking_next_u64(&mut self) -> u64 { + let mut bytes = [0; 8]; + self.blocking_fill_bytes(&mut bytes); + u64::from_ne_bytes(bytes) + } } -impl<'d, T: Instance> Drop for Rng<'d, T> { +impl<'d, T: Instance, M: Mode> Drop for Rng<'d, T, M> { fn drop(&mut self) { self.stop(); critical_section::with(|cs| { @@ -182,31 +221,37 @@ impl<'d, T: Instance> Drop for Rng<'d, T> { } } -impl<'d, T: Instance> rand_core::RngCore for Rng<'d, T> { +impl<'d, T: Instance, M: Mode> rand_core_06::RngCore for Rng<'d, T, M> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } - fn next_u32(&mut self) -> u32 { - let mut bytes = [0; 4]; - self.blocking_fill_bytes(&mut bytes); - // We don't care about the endianness, so just use the native one. - u32::from_ne_bytes(bytes) + self.blocking_next_u32() } - fn next_u64(&mut self) -> u64 { - let mut bytes = [0; 8]; - self.blocking_fill_bytes(&mut bytes); - u64::from_ne_bytes(bytes) + self.blocking_next_u64() } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { self.blocking_fill_bytes(dest); Ok(()) } } -impl<'d, T: Instance> rand_core::CryptoRng for Rng<'d, T> {} +impl<'d, T: Instance, M: Mode> rand_core_06::CryptoRng for Rng<'d, T, M> {} + +impl<'d, T: Instance, M: Mode> rand_core_09::RngCore for Rng<'d, T, M> { + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } +} + +impl<'d, T: Instance, M: Mode> rand_core_09::CryptoRng for Rng<'d, T, M> {} /// Peripheral static state pub(crate) struct State { @@ -244,13 +289,13 @@ impl InnerState { } pub(crate) trait SealedInstance { - fn regs() -> &'static crate::pac::rng::RegisterBlock; + fn regs() -> pac::rng::Rng; fn state() -> &'static State; } /// RNG peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -258,8 +303,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_rng { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::rng::SealedInstance for peripherals::$type { - fn regs() -> &'static crate::pac::rng::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> crate::pac::rng::Rng { + pac::$pac_type } fn state() -> &'static crate::rng::State { static STATE: crate::rng::State = crate::rng::State::new(); diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index bbfa9b3b9..92b6fb01f 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -3,23 +3,20 @@ #![macro_use] use core::future::poll_fn; +use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_hal_internal::{impl_peripheral, Peri}; use embassy_sync::waitqueue::AtomicWaker; -use pac::{saadc, SAADC}; -use saadc::ch::config::{GAIN_A, REFSEL_A, RESP_A, TACQ_A}; -// We treat the positive and negative channels with the same enum values to keep our type tidy and given they are the same -pub(crate) use saadc::ch::pselp::PSELP_A as InputChannel; -use saadc::oversample::OVERSAMPLE_A; -use saadc::resolution::VAL_A; +pub(crate) use vals::Psel as InputChannel; use crate::interrupt::InterruptExt; +use crate::pac::saadc::vals; use crate::ppi::{ConfigurableChannel, Event, Ppi, Task}; use crate::timer::{Frequency, Instance as TimerInstance, Timer}; -use crate::{interrupt, pac, peripherals, Peripheral}; +use crate::{interrupt, pac, peripherals}; /// SAADC error #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -34,20 +31,20 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let r = unsafe { &*SAADC::ptr() }; + let r = pac::SAADC; - if r.events_calibratedone.read().bits() != 0 { - r.intenclr.write(|w| w.calibratedone().clear()); + if r.events_calibratedone().read() != 0 { + r.intenclr().write(|w| w.set_calibratedone(true)); WAKER.wake(); } - if r.events_end.read().bits() != 0 { - r.intenclr.write(|w| w.end().clear()); + if r.events_end().read() != 0 { + r.intenclr().write(|w| w.set_end(true)); WAKER.wake(); } - if r.events_started.read().bits() != 0 { - r.intenclr.write(|w| w.started().clear()); + if r.events_started().read() != 0 { + r.intenclr().write(|w| w.set_started(true)); WAKER.wake(); } } @@ -91,37 +88,32 @@ pub struct ChannelConfig<'d> { /// Acquisition time in microseconds. pub time: Time, /// Positive channel to sample - p_channel: PeripheralRef<'d, AnyInput>, + p_channel: AnyInput<'d>, /// An optional negative channel to sample - n_channel: Option>, + n_channel: Option>, } impl<'d> ChannelConfig<'d> { /// Default configuration for single ended channel sampling. - pub fn single_ended(input: impl Peripheral

+ 'd) -> Self { - into_ref!(input); + pub fn single_ended(input: impl Input + 'd) -> Self { Self { reference: Reference::INTERNAL, gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, time: Time::_10US, - p_channel: input.map_into(), + p_channel: input.degrade_saadc(), n_channel: None, } } /// Default configuration for differential channel sampling. - pub fn differential( - p_input: impl Peripheral

+ 'd, - n_input: impl Peripheral

+ 'd, - ) -> Self { - into_ref!(p_input, n_input); + pub fn differential(p_input: impl Input + 'd, n_input: impl Input + 'd) -> Self { Self { reference: Reference::INTERNAL, gain: Gain::GAIN1_6, resistor: Resistor::BYPASS, time: Time::_10US, - p_channel: p_input.map_into(), - n_channel: Some(n_input.map_into()), + p_channel: p_input.degrade_saadc(), + n_channel: Some(n_input.degrade_saadc()), } } } @@ -137,57 +129,47 @@ pub enum CallbackResult { /// One-shot and continuous SAADC. pub struct Saadc<'d, const N: usize> { - _p: PeripheralRef<'d, peripherals::SAADC>, + _p: Peri<'d, peripherals::SAADC>, } impl<'d, const N: usize> Saadc<'d, N> { /// Create a new SAADC driver. pub fn new( - saadc: impl Peripheral

+ 'd, + saadc: Peri<'d, peripherals::SAADC>, _irq: impl interrupt::typelevel::Binding + 'd, config: Config, channel_configs: [ChannelConfig; N], ) -> Self { - into_ref!(saadc); - - let r = unsafe { &*SAADC::ptr() }; + let r = pac::SAADC; let Config { resolution, oversample } = config; // Configure channels - r.enable.write(|w| w.enable().enabled()); - r.resolution.write(|w| w.val().variant(resolution.into())); - r.oversample.write(|w| w.oversample().variant(oversample.into())); + r.enable().write(|w| w.set_enable(true)); + r.resolution().write(|w| w.set_val(resolution.into())); + r.oversample().write(|w| w.set_oversample(oversample.into())); for (i, cc) in channel_configs.iter().enumerate() { - r.ch[i].pselp.write(|w| w.pselp().variant(cc.p_channel.channel())); + r.ch(i).pselp().write(|w| w.set_pselp(cc.p_channel.channel())); if let Some(n_channel) = &cc.n_channel { - r.ch[i] - .pseln - .write(|w| unsafe { w.pseln().bits(n_channel.channel() as u8) }); + r.ch(i).pseln().write(|w| w.set_pseln(n_channel.channel())); } - r.ch[i].config.write(|w| { - w.refsel().variant(cc.reference.into()); - w.gain().variant(cc.gain.into()); - w.tacq().variant(cc.time.into()); - if cc.n_channel.is_none() { - w.mode().se(); - } else { - w.mode().diff(); - } - w.resp().variant(cc.resistor.into()); - w.resn().bypass(); - if !matches!(oversample, Oversample::BYPASS) { - w.burst().enabled(); - } else { - w.burst().disabled(); - } - w + r.ch(i).config().write(|w| { + w.set_refsel(cc.reference.into()); + w.set_gain(cc.gain.into()); + w.set_tacq(cc.time.into()); + w.set_mode(match cc.n_channel { + None => vals::ConfigMode::SE, + Some(_) => vals::ConfigMode::DIFF, + }); + w.set_resp(cc.resistor.into()); + w.set_resn(vals::Resn::BYPASS); + w.set_burst(!matches!(oversample, Oversample::BYPASS)); }); } // Disable all events interrupts - r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); + r.intenclr().write(|w| w.0 = 0x003F_FFFF); interrupt::SAADC.unpend(); unsafe { interrupt::SAADC.enable() }; @@ -195,8 +177,8 @@ impl<'d, const N: usize> Saadc<'d, N> { Self { _p: saadc } } - fn regs() -> &'static saadc::RegisterBlock { - unsafe { &*SAADC::ptr() } + fn regs() -> pac::saadc::Saadc { + pac::SAADC } /// Perform SAADC calibration. Completes when done. @@ -204,13 +186,13 @@ impl<'d, const N: usize> Saadc<'d, N> { let r = Self::regs(); // Reset and enable the end event - r.events_calibratedone.reset(); - r.intenset.write(|w| w.calibratedone().set()); + r.events_calibratedone().write_value(0); + r.intenset().write(|w| w.set_calibratedone(true)); // Order is important compiler_fence(Ordering::SeqCst); - r.tasks_calibrateoffset.write(|w| unsafe { w.bits(1) }); + r.tasks_calibrateoffset().write_value(1); // Wait for 'calibratedone' event. poll_fn(|cx| { @@ -218,8 +200,8 @@ impl<'d, const N: usize> Saadc<'d, N> { WAKER.register(cx.waker()); - if r.events_calibratedone.read().bits() != 0 { - r.events_calibratedone.reset(); + if r.events_calibratedone().read() != 0 { + r.events_calibratedone().write_value(0); return Poll::Ready(()); } @@ -239,19 +221,19 @@ impl<'d, const N: usize> Saadc<'d, N> { let r = Self::regs(); // Set up the DMA - r.result.ptr.write(|w| unsafe { w.ptr().bits(buf.as_mut_ptr() as u32) }); - r.result.maxcnt.write(|w| unsafe { w.maxcnt().bits(N as _) }); + r.result().ptr().write_value(buf.as_mut_ptr() as u32); + r.result().maxcnt().write(|w| w.set_maxcnt(N as _)); // Reset and enable the end event - r.events_end.reset(); - r.intenset.write(|w| w.end().set()); + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); // Don't reorder the ADC start event before the previous writes. Hopefully self // wouldn't happen anyway. compiler_fence(Ordering::SeqCst); - r.tasks_start.write(|w| unsafe { w.bits(1) }); - r.tasks_sample.write(|w| unsafe { w.bits(1) }); + r.tasks_start().write_value(1); + r.tasks_sample().write_value(1); // Wait for 'end' event. poll_fn(|cx| { @@ -259,8 +241,8 @@ impl<'d, const N: usize> Saadc<'d, N> { WAKER.register(cx.waker()); - if r.events_end.read().bits() != 0 { - r.events_end.reset(); + if r.events_end().read() != 0 { + r.events_end().write_value(0); return Poll::Ready(()); } @@ -296,9 +278,9 @@ impl<'d, const N: usize> Saadc<'d, N> { pub async fn run_task_sampler( &mut self, - timer: &mut T, - ppi_ch1: &mut impl ConfigurableChannel, - ppi_ch2: &mut impl ConfigurableChannel, + timer: Peri<'_, T>, + ppi_ch1: Peri<'_, impl ConfigurableChannel>, + ppi_ch2: Peri<'_, impl ConfigurableChannel>, frequency: Frequency, sample_counter: u32, bufs: &mut [[[i16; N]; N0]; 2], @@ -311,8 +293,11 @@ impl<'d, const N: usize> Saadc<'d, N> { // We want the task start to effectively short with the last one ending so // we don't miss any samples. It'd be great for the SAADC to offer a SHORTS // register instead, but it doesn't, so we must use PPI. - let mut start_ppi = - Ppi::new_one_to_one(ppi_ch1, Event::from_reg(&r.events_end), Task::from_reg(&r.tasks_start)); + let mut start_ppi = Ppi::new_one_to_one( + ppi_ch1, + Event::from_reg(r.events_end()), + Task::from_reg(r.tasks_start()), + ); start_ppi.enable(); let timer = Timer::new(timer); @@ -322,7 +307,7 @@ impl<'d, const N: usize> Saadc<'d, N> { let timer_cc = timer.cc(0); - let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer_cc.event_compare(), Task::from_reg(&r.tasks_sample)); + let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer_cc.event_compare(), Task::from_reg(r.tasks_sample())); timer.start(); @@ -355,43 +340,37 @@ impl<'d, const N: usize> Saadc<'d, N> { // Establish mode and sample rate match sample_rate_divisor { Some(sr) => { - r.samplerate.write(|w| unsafe { - w.cc().bits(sr); - w.mode().timers(); - w + r.samplerate().write(|w| { + w.set_cc(sr); + w.set_mode(vals::SamplerateMode::TIMERS); }); - r.tasks_sample.write(|w| unsafe { w.bits(1) }); // Need to kick-start the internal timer + r.tasks_sample().write_value(1); // Need to kick-start the internal timer } - None => r.samplerate.write(|w| unsafe { - w.cc().bits(0); - w.mode().task(); - w + None => r.samplerate().write(|w| { + w.set_cc(0); + w.set_mode(vals::SamplerateMode::TASK); }), } // Set up the initial DMA - r.result - .ptr - .write(|w| unsafe { w.ptr().bits(bufs[0].as_mut_ptr() as u32) }); - r.result.maxcnt.write(|w| unsafe { w.maxcnt().bits((N0 * N) as _) }); + r.result().ptr().write_value(bufs[0].as_mut_ptr() as u32); + r.result().maxcnt().write(|w| w.set_maxcnt((N0 * N) as _)); // Reset and enable the events - r.events_end.reset(); - r.events_started.reset(); - r.intenset.write(|w| { - w.end().set(); - w.started().set(); - w + r.events_end().write_value(0); + r.events_started().write_value(0); + r.intenset().write(|w| { + w.set_end(true); + w.set_started(true); }); // Don't reorder the ADC start event before the previous writes. Hopefully self // wouldn't happen anyway. compiler_fence(Ordering::SeqCst); - r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.tasks_start().write_value(1); let mut inited = false; - let mut current_buffer = 0; // Wait for events and complete when the sampler indicates it has had enough. @@ -400,11 +379,11 @@ impl<'d, const N: usize> Saadc<'d, N> { WAKER.register(cx.waker()); - if r.events_end.read().bits() != 0 { + if r.events_end().read() != 0 { compiler_fence(Ordering::SeqCst); - r.events_end.reset(); - r.intenset.write(|w| w.end().set()); + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); match callback(&bufs[current_buffer]) { CallbackResult::Continue => { @@ -417,9 +396,9 @@ impl<'d, const N: usize> Saadc<'d, N> { } } - if r.events_started.read().bits() != 0 { - r.events_started.reset(); - r.intenset.write(|w| w.started().set()); + if r.events_started().read() != 0 { + r.events_started().write_value(0); + r.intenset().write(|w| w.set_started(true)); if !inited { init(); @@ -427,9 +406,7 @@ impl<'d, const N: usize> Saadc<'d, N> { } let next_buffer = 1 - current_buffer; - r.result - .ptr - .write(|w| unsafe { w.ptr().bits(bufs[next_buffer].as_mut_ptr() as u32) }); + r.result().ptr().write_value(bufs[next_buffer].as_mut_ptr() as u32); } Poll::Pending @@ -447,11 +424,11 @@ impl<'d, const N: usize> Saadc<'d, N> { compiler_fence(Ordering::SeqCst); - r.events_stopped.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.events_stopped().write_value(0); + r.tasks_stop().write_value(1); - while r.events_stopped.read().bits() == 0 {} - r.events_stopped.reset(); + while r.events_stopped().read() == 0 {} + r.events_stopped().write_value(0); } } @@ -481,21 +458,25 @@ impl<'d> Saadc<'d, 1> { impl<'d, const N: usize> Drop for Saadc<'d, N> { fn drop(&mut self) { let r = Self::regs(); - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(false)); + for i in 0..N { + r.ch(i).pselp().write(|w| w.set_pselp(InputChannel::NC)); + r.ch(i).pseln().write(|w| w.set_pseln(InputChannel::NC)); + } } } -impl From for GAIN_A { +impl From for vals::Gain { fn from(gain: Gain) -> Self { match gain { - Gain::GAIN1_6 => GAIN_A::GAIN1_6, - Gain::GAIN1_5 => GAIN_A::GAIN1_5, - Gain::GAIN1_4 => GAIN_A::GAIN1_4, - Gain::GAIN1_3 => GAIN_A::GAIN1_3, - Gain::GAIN1_2 => GAIN_A::GAIN1_2, - Gain::GAIN1 => GAIN_A::GAIN1, - Gain::GAIN2 => GAIN_A::GAIN2, - Gain::GAIN4 => GAIN_A::GAIN4, + Gain::GAIN1_6 => vals::Gain::GAIN1_6, + Gain::GAIN1_5 => vals::Gain::GAIN1_5, + Gain::GAIN1_4 => vals::Gain::GAIN1_4, + Gain::GAIN1_3 => vals::Gain::GAIN1_3, + Gain::GAIN1_2 => vals::Gain::GAIN1_2, + Gain::GAIN1 => vals::Gain::GAIN1, + Gain::GAIN2 => vals::Gain::GAIN2, + Gain::GAIN4 => vals::Gain::GAIN4, } } } @@ -522,11 +503,11 @@ pub enum Gain { GAIN4 = 7, } -impl From for REFSEL_A { +impl From for vals::Refsel { fn from(reference: Reference) -> Self { match reference { - Reference::INTERNAL => REFSEL_A::INTERNAL, - Reference::VDD1_4 => REFSEL_A::VDD1_4, + Reference::INTERNAL => vals::Refsel::INTERNAL, + Reference::VDD1_4 => vals::Refsel::VDD1_4, } } } @@ -541,13 +522,13 @@ pub enum Reference { VDD1_4 = 1, } -impl From for RESP_A { +impl From for vals::Resp { fn from(resistor: Resistor) -> Self { match resistor { - Resistor::BYPASS => RESP_A::BYPASS, - Resistor::PULLDOWN => RESP_A::PULLDOWN, - Resistor::PULLUP => RESP_A::PULLUP, - Resistor::VDD1_2 => RESP_A::VDD1_2, + Resistor::BYPASS => vals::Resp::BYPASS, + Resistor::PULLDOWN => vals::Resp::PULLDOWN, + Resistor::PULLUP => vals::Resp::PULLUP, + Resistor::VDD1_2 => vals::Resp::VDD1_2, } } } @@ -566,15 +547,15 @@ pub enum Resistor { VDD1_2 = 3, } -impl From

+ Sized + 'static { +pub trait Input: SealedInput + Sized { /// Convert this SAADC input to a type-erased `AnyInput`. /// /// This allows using several inputs in situations that might require /// them to be the same type, like putting them in an array. - fn degrade_saadc(self) -> AnyInput { + fn degrade_saadc<'a>(self) -> AnyInput<'a> + where + Self: 'a, + { AnyInput { channel: self.channel(), + _phantom: core::marker::PhantomData, } } } @@ -684,23 +669,36 @@ pub trait Input: SealedInput + Into + Peripheral

+ Sized + ' /// /// This allows using several inputs in situations that might require /// them to be the same type, like putting them in an array. -pub struct AnyInput { +pub struct AnyInput<'a> { channel: InputChannel, + _phantom: PhantomData<&'a ()>, } -impl_peripheral!(AnyInput); +impl<'a> AnyInput<'a> { + /// Reborrow into a "child" AnyInput. + /// + /// `self` will stay borrowed until the child AnyInput is dropped. + pub fn reborrow(&mut self) -> AnyInput<'_> { + // safety: we're returning the clone inside a new Peripheral that borrows + // self, so user code can't use both at the same time. + Self { + channel: self.channel, + _phantom: PhantomData, + } + } +} -impl SealedInput for AnyInput { +impl SealedInput for AnyInput<'_> { fn channel(&self) -> InputChannel { self.channel } } -impl Input for AnyInput {} +impl Input for AnyInput<'_> {} macro_rules! impl_saadc_input { ($pin:ident, $ch:ident) => { - impl_saadc_input!(@local, crate::peripherals::$pin, $ch); + impl_saadc_input!(@local, crate::Peri<'_, crate::peripherals::$pin>, $ch); }; (@local, $pin:ty, $ch:ident) => { impl crate::saadc::SealedInput for $pin { @@ -709,12 +707,6 @@ macro_rules! impl_saadc_input { } } impl crate::saadc::Input for $pin {} - - impl From<$pin> for crate::saadc::AnyInput { - fn from(val: $pin) -> Self { - crate::saadc::Input::degrade_saadc(val) - } - } }; } @@ -726,7 +718,7 @@ impl_peripheral!(VddInput); #[cfg(not(feature = "_nrf91"))] impl_saadc_input!(@local, VddInput, VDD); #[cfg(feature = "_nrf91")] -impl_saadc_input!(@local, VddInput, VDDGPIO); +impl_saadc_input!(@local, VddInput, VDD_GPIO); /// A dummy `Input` pin implementation for SAADC peripheral sampling from the /// VDDH / 5 voltage. diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index 52660711a..59f5b6d58 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -10,17 +10,18 @@ use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; 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; +pub use pac::spim::vals::{Frequency, Order as BitOrder}; use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; use crate::gpio::{self, convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits, SealedPin as _}; use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::spim::vals; use crate::util::slice_in_ram_or; -use crate::{interrupt, pac, Peripheral}; +use crate::{interrupt, pac}; /// SPIM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -33,6 +34,7 @@ pub enum Error { /// SPIM configuration. #[non_exhaustive] +#[derive(Clone)] pub struct Config { /// Frequency pub frequency: Frequency, @@ -54,9 +56,6 @@ pub struct Config { /// Drive strength for the MOSI line. pub mosi_drive: OutputDrive, - - /// Drive strength for the MISO line. - pub miso_drive: OutputDrive, } impl Default for Config { @@ -68,7 +67,6 @@ impl Default for Config { orc: 0x00, sck_drive: OutputDrive::HighDrive, mosi_drive: OutputDrive::HighDrive, - miso_drive: OutputDrive::HighDrive, } } } @@ -87,102 +85,93 @@ impl interrupt::typelevel::Handler for InterruptHandl { // 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 { + if r.events_started().read() != 0 { s.waker.wake(); - r.intenclr.write(|w| w.started().clear()); + r.intenclr().write(|w| w.set_started(true)); } } - if r.events_end.read().bits() != 0 { + if r.events_end().read() != 0 { s.waker.wake(); - r.intenclr.write(|w| w.end().clear()); + r.intenclr().write(|w| w.set_end(true)); } } } /// SPIM driver. pub struct Spim<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } impl<'d, T: Instance> Spim<'d, T> { /// Create a new SPIM driver. pub fn new( - spim: impl Peripheral

+ 'd, + spim: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - sck: impl Peripheral

+ 'd, - miso: impl Peripheral

+ 'd, - mosi: impl Peripheral

+ 'd, + sck: Peri<'d, impl GpioPin>, + miso: Peri<'d, impl GpioPin>, + mosi: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(sck, miso, mosi); - Self::new_inner( - spim, - Some(sck.map_into()), - Some(miso.map_into()), - Some(mosi.map_into()), - config, - ) + Self::new_inner(spim, Some(sck.into()), Some(miso.into()), Some(mosi.into()), config) } /// Create a new SPIM driver, capable of TX only (MOSI only). pub fn new_txonly( - spim: impl Peripheral

+ 'd, + spim: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - sck: impl Peripheral

+ 'd, - mosi: impl Peripheral

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

+ 'd, + spim: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - sck: impl Peripheral

+ 'd, - miso: impl Peripheral

+ 'd, + sck: Peri<'d, impl GpioPin>, + miso: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(sck, miso); - Self::new_inner(spim, Some(sck.map_into()), Some(miso.map_into()), None, config) + Self::new_inner(spim, Some(sck.into()), Some(miso.into()), None, config) } /// Create a new SPIM driver, capable of TX only (MOSI only), without SCK pin. pub fn new_txonly_nosck( - spim: impl Peripheral

+ 'd, + spim: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - mosi: impl Peripheral

+ 'd, + mosi: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(mosi); - Self::new_inner(spim, None, None, Some(mosi.map_into()), config) + Self::new_inner(spim, None, None, Some(mosi.into()), config) } fn new_inner( - spim: impl Peripheral

+ 'd, - sck: Option>, - miso: Option>, - mosi: Option>, + spim: Peri<'d, T>, + sck: Option>, + miso: Option>, + mosi: Option>, config: Config, ) -> Self { - into_ref!(spim); - let r = T::regs(); // Configure pins if let Some(sck) = &sck { - sck.conf() - .write(|w| w.dir().output().drive().variant(convert_drive(config.sck_drive))); + sck.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + convert_drive(w, config.sck_drive); + }); } if let Some(mosi) = &mosi { - mosi.conf() - .write(|w| w.dir().output().drive().variant(convert_drive(config.mosi_drive))); + mosi.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + convert_drive(w, config.mosi_drive); + }); } if let Some(miso) = &miso { - miso.conf() - .write(|w| w.input().connect().drive().variant(convert_drive(config.miso_drive))); + miso.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); } match config.mode.polarity { @@ -205,12 +194,12 @@ impl<'d, T: Instance> Spim<'d, T> { } // Select pins. - r.psel.sck.write(|w| unsafe { w.bits(sck.psel_bits()) }); - r.psel.mosi.write(|w| unsafe { w.bits(mosi.psel_bits()) }); - r.psel.miso.write(|w| unsafe { w.bits(miso.psel_bits()) }); + r.psel().sck().write_value(sck.psel_bits()); + r.psel().mosi().write_value(mosi.psel_bits()); + r.psel().miso().write_value(miso.psel_bits()); // Enable SPIM instance. - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); let mut spim = Self { _p: spim }; @@ -218,7 +207,7 @@ impl<'d, T: Instance> Spim<'d, T> { Self::set_config(&mut spim, &config).unwrap(); // Disable all events interrupts - r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; @@ -241,13 +230,13 @@ impl<'d, T: Instance> Spim<'d, T> { // Set up the DMA read. let (rx_ptr, rx_len) = xfer_params(rx as *mut u8 as _, rx.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 _) }); + r.rxd().ptr().write_value(rx_ptr); + r.rxd().maxcnt().write(|w| w.set_maxcnt(rx_len as _)); // Set up the DMA write. let (tx_ptr, tx_len) = xfer_params(tx as *const u8 as _, tx.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 _) }); + r.txd().ptr().write_value(tx_ptr); + r.txd().maxcnt().write(|w| w.set_maxcnt(tx_len as _)); /* trace!("XFER: offset: {}, length: {}", offset, length); @@ -259,26 +248,26 @@ impl<'d, T: Instance> Spim<'d, T> { if offset == 0 { let s = T::state(); - r.events_started.reset(); + r.events_started().write_value(0); // Set rx/tx buffer lengths to 0... - r.txd.maxcnt.reset(); - r.rxd.maxcnt.reset(); + r.txd().maxcnt().write(|_| ()); + r.rxd().maxcnt().write(|_| ()); // ...and keep track of original buffer lengths... s.tx.store(tx_len as _, Ordering::Relaxed); s.rx.store(rx_len as _, Ordering::Relaxed); // ...signalling the start of the fake transfer. - r.intenset.write(|w| w.started().bit(true)); + r.intenset().write(|w| w.set_started(true)); } // Reset and enable the event - r.events_end.reset(); - r.intenset.write(|w| w.end().set()); + r.events_end().write_value(0); + r.intenset().write(|w| w.set_end(true)); // Start SPI transaction. - r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.tasks_start().write_value(1); } fn blocking_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) { @@ -290,7 +279,7 @@ impl<'d, T: Instance> Spim<'d, T> { } // Wait for 'end' event. - while T::regs().events_end.read().bits() == 0 {} + while T::regs().events_end().read() == 0 {} compiler_fence(Ordering::SeqCst); } @@ -338,7 +327,7 @@ impl<'d, T: Instance> Spim<'d, T> { // Wait for 'end' event. poll_fn(|cx| { T::state().waker.register(cx.waker()); - if T::regs().events_end.read().bits() != 0 { + if T::regs().events_end().read() != 0 { return Poll::Ready(()); } @@ -442,24 +431,20 @@ impl<'d, T: Instance> Spim<'d, T> { #[cfg(feature = "_nrf52832_anomaly_109")] fn nrf52832_dma_workaround_status(&mut self) -> Poll<()> { let r = T::regs(); - if r.events_started.read().bits() != 0 { + if r.events_started().read() != 0 { let s = T::state(); // Handle the first "fake" transmission - r.events_started.reset(); - r.events_end.reset(); + r.events_started().write_value(0); + r.events_end().write_value(0); // Update DMA registers with correct rx/tx buffer sizes - r.rxd - .maxcnt - .write(|w| unsafe { w.maxcnt().bits(s.rx.load(Ordering::Relaxed)) }); - r.txd - .maxcnt - .write(|w| unsafe { w.maxcnt().bits(s.tx.load(Ordering::Relaxed)) }); + r.rxd().maxcnt().write(|w| w.set_maxcnt(s.rx.load(Ordering::Relaxed))); + r.txd().maxcnt().write(|w| w.set_maxcnt(s.tx.load(Ordering::Relaxed))); - r.intenset.write(|w| w.end().set()); + r.intenset().write(|w| w.set_end(true)); // ... and start actual, hopefully glitch-free transmission - r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.tasks_start().write_value(1); return Poll::Ready(()); } Poll::Pending @@ -474,11 +459,11 @@ impl<'d, T: Instance> Drop for Spim<'d, T> { // disable! let r = T::regs(); - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); - gpio::deconfigure_pin(r.psel.sck.read().bits()); - gpio::deconfigure_pin(r.psel.miso.read().bits()); - gpio::deconfigure_pin(r.psel.mosi.read().bits()); + gpio::deconfigure_pin(r.psel().sck().read()); + gpio::deconfigure_pin(r.psel().miso().read()); + gpio::deconfigure_pin(r.psel().mosi().read()); // Disable all events interrupts T::Interrupt::disable(); @@ -508,13 +493,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::spim0::RegisterBlock; + fn regs() -> pac::spim::Spim; fn state() -> &'static State; } /// SPIM peripheral instance #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static { +pub trait Instance: SealedInstance + PeripheralType + 'static { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -522,8 +507,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static { macro_rules! impl_spim { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::spim::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::spim0::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::spim::Spim { + pac::$pac_type } fn state() -> &'static crate::spim::State { static STATE: crate::spim::State = crate::spim::State::new(); @@ -621,40 +606,35 @@ impl<'d, T: Instance> SetConfig for Spim<'d, T> { let r = T::regs(); // Configure mode. let mode = config.mode; - r.config.write(|w| { + r.config().write(|w| { + w.set_order(config.bit_order); match mode { MODE_0 => { - w.order().variant(config.bit_order); - w.cpol().active_high(); - w.cpha().leading(); + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::LEADING); } MODE_1 => { - w.order().variant(config.bit_order); - w.cpol().active_high(); - w.cpha().trailing(); + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::TRAILING); } MODE_2 => { - w.order().variant(config.bit_order); - w.cpol().active_low(); - w.cpha().leading(); + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::LEADING); } MODE_3 => { - w.order().variant(config.bit_order); - w.cpol().active_low(); - w.cpha().trailing(); + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::TRAILING); } } - - w }); // Configure frequency. let frequency = config.frequency; - r.frequency.write(|w| w.frequency().variant(frequency)); + r.frequency().write(|w| w.set_frequency(frequency)); // Set over-read character let orc = config.orc; - r.orc.write(|w| unsafe { w.orc().bits(orc) }); + r.orc().write(|w| w.set_orc(orc)); Ok(()) } diff --git a/embassy-nrf/src/spis.rs b/embassy-nrf/src/spis.rs index e98b34369..2a3928d25 100644 --- a/embassy-nrf/src/spis.rs +++ b/embassy-nrf/src/spis.rs @@ -7,16 +7,18 @@ use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; 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; +pub use pac::spis::vals::Order as BitOrder; use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; -use crate::gpio::{self, AnyPin, Pin as GpioPin, SealedPin as _}; +use crate::gpio::{self, convert_drive, AnyPin, OutputDrive, Pin as GpioPin, SealedPin as _}; use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::spis::vals; use crate::util::slice_in_ram_or; -use crate::{interrupt, pac, Peripheral}; +use crate::{interrupt, pac}; /// SPIS error #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -54,6 +56,9 @@ pub struct Config { /// Automatically make the firmware side acquire the semaphore on transfer end. pub auto_acquire: bool, + + /// Drive strength for the MISO line. + pub miso_drive: OutputDrive, } impl Default for Config { @@ -64,6 +69,7 @@ impl Default for Config { orc: 0x00, def: 0x00, auto_acquire: true, + miso_drive: OutputDrive::HighDrive, } } } @@ -78,129 +84,112 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); - if r.events_end.read().bits() != 0 { + if r.events_end().read() != 0 { s.waker.wake(); - r.intenclr.write(|w| w.end().clear()); + r.intenclr().write(|w| w.set_end(true)); } - if r.events_acquired.read().bits() != 0 { + if r.events_acquired().read() != 0 { s.waker.wake(); - r.intenclr.write(|w| w.acquired().clear()); + r.intenclr().write(|w| w.set_acquired(true)); } } } /// SPIS driver. pub struct Spis<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } impl<'d, T: Instance> Spis<'d, T> { /// Create a new SPIS driver. pub fn new( - spis: impl Peripheral

+ 'd, + spis: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - cs: impl Peripheral

+ 'd, - sck: impl Peripheral

+ 'd, - miso: impl Peripheral

+ 'd, - mosi: impl Peripheral

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

+ 'd, + spis: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - cs: impl Peripheral

+ 'd, - sck: impl Peripheral

+ 'd, - miso: impl Peripheral

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

+ 'd, + spis: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - cs: impl Peripheral

+ 'd, - sck: impl Peripheral

+ 'd, - mosi: impl Peripheral

+ 'd, + cs: Peri<'d, impl GpioPin>, + sck: Peri<'d, impl GpioPin>, + mosi: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(cs, sck, mosi); - Self::new_inner( - spis, - cs.map_into(), - Some(sck.map_into()), - None, - Some(mosi.map_into()), - config, - ) + Self::new_inner(spis, cs.into(), Some(sck.into()), None, Some(mosi.into()), config) } /// Create a new SPIS driver, capable of TX only (MISO only) without SCK pin. pub fn new_txonly_nosck( - spis: impl Peripheral

+ 'd, + spis: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - cs: impl Peripheral

+ 'd, - miso: impl Peripheral

+ 'd, + cs: Peri<'d, impl GpioPin>, + miso: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(cs, miso); - Self::new_inner(spis, cs.map_into(), None, Some(miso.map_into()), None, config) + Self::new_inner(spis, cs.into(), None, Some(miso.into()), None, config) } fn new_inner( - spis: impl Peripheral

+ 'd, - cs: PeripheralRef<'d, AnyPin>, - sck: Option>, - miso: Option>, - mosi: Option>, + spis: Peri<'d, T>, + cs: Peri<'d, AnyPin>, + sck: Option>, + miso: Option>, + mosi: Option>, config: Config, ) -> Self { compiler_fence(Ordering::SeqCst); - into_ref!(spis, cs); - let r = T::regs(); // Configure pins. - cs.conf().write(|w| w.input().connect().drive().h0h1()); - r.psel.csn.write(|w| unsafe { w.bits(cs.psel_bits()) }); + cs.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().csn().write_value(cs.psel_bits()); if let Some(sck) = &sck { - sck.conf().write(|w| w.input().connect().drive().h0h1()); - r.psel.sck.write(|w| unsafe { w.bits(sck.psel_bits()) }); + sck.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().sck().write_value(sck.psel_bits()); } if let Some(mosi) = &mosi { - mosi.conf().write(|w| w.input().connect().drive().h0h1()); - r.psel.mosi.write(|w| unsafe { w.bits(mosi.psel_bits()) }); + mosi.conf().write(|w| w.set_input(gpiovals::Input::CONNECT)); + r.psel().mosi().write_value(mosi.psel_bits()); } if let Some(miso) = &miso { - miso.conf().write(|w| w.dir().output().drive().h0h1()); - r.psel.miso.write(|w| unsafe { w.bits(miso.psel_bits()) }); + miso.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + convert_drive(w, config.miso_drive); + }); + r.psel().miso().write_value(miso.psel_bits()); } // Enable SPIS instance. - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); let mut spis = Self { _p: spis }; @@ -208,7 +197,7 @@ impl<'d, T: Instance> Spis<'d, T> { Self::set_config(&mut spis, &config).unwrap(); // Disable all events interrupts. - r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; @@ -229,21 +218,21 @@ impl<'d, T: Instance> Spis<'d, T> { if tx.len() > EASY_DMA_SIZE { return Err(Error::TxBufferTooLong); } - r.txd.ptr.write(|w| unsafe { w.ptr().bits(tx as *const u8 as _) }); - r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx.len() as _) }); + r.txd().ptr().write_value(tx as *const u8 as _); + r.txd().maxcnt().write(|w| w.set_maxcnt(tx.len() as _)); // Set up the DMA read. if rx.len() > EASY_DMA_SIZE { return Err(Error::RxBufferTooLong); } - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(rx as *mut u8 as _) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx.len() as _) }); + r.rxd().ptr().write_value(rx as *mut u8 as _); + r.rxd().maxcnt().write(|w| w.set_maxcnt(rx.len() as _)); // Reset end event. - r.events_end.reset(); + r.events_end().write_value(0); // Release the semaphore. - r.tasks_release.write(|w| unsafe { w.bits(1) }); + r.tasks_release().write_value(1); Ok(()) } @@ -253,20 +242,20 @@ impl<'d, T: Instance> Spis<'d, T> { let r = T::regs(); // Acquire semaphore. - if r.semstat.read().bits() != 1 { - r.events_acquired.reset(); - r.tasks_acquire.write(|w| unsafe { w.bits(1) }); + if r.semstat().read().0 != 1 { + r.events_acquired().write_value(0); + r.tasks_acquire().write_value(1); // Wait until CPU has acquired the semaphore. - while r.semstat.read().bits() != 1 {} + while r.semstat().read().0 != 1 {} } self.prepare(rx, tx)?; // Wait for 'end' event. - while r.events_end.read().bits() == 0 {} + while r.events_end().read() == 0 {} - let n_rx = r.rxd.amount.read().bits() as usize; - let n_tx = r.txd.amount.read().bits() as usize; + let n_rx = r.rxd().amount().read().0 as usize; + let n_tx = r.txd().amount().read().0 as usize; compiler_fence(Ordering::SeqCst); @@ -291,22 +280,25 @@ impl<'d, T: Instance> Spis<'d, T> { let s = T::state(); // Clear status register. - r.status.write(|w| w.overflow().clear().overread().clear()); + r.status().write(|w| { + w.set_overflow(true); + w.set_overread(true); + }); // Acquire semaphore. - if r.semstat.read().bits() != 1 { + if r.semstat().read().0 != 1 { // Reset and enable the acquire event. - r.events_acquired.reset(); - r.intenset.write(|w| w.acquired().set()); + r.events_acquired().write_value(0); + r.intenset().write(|w| w.set_acquired(true)); // Request acquiring the SPIS semaphore. - r.tasks_acquire.write(|w| unsafe { w.bits(1) }); + r.tasks_acquire().write_value(1); // Wait until CPU has acquired the semaphore. poll_fn(|cx| { s.waker.register(cx.waker()); - if r.events_acquired.read().bits() == 1 { - r.events_acquired.reset(); + if r.events_acquired().read() == 1 { + r.events_acquired().write_value(0); return Poll::Ready(()); } Poll::Pending @@ -317,19 +309,19 @@ impl<'d, T: Instance> Spis<'d, T> { self.prepare(rx, tx)?; // Wait for 'end' event. - r.intenset.write(|w| w.end().set()); + r.intenset().write(|w| w.set_end(true)); poll_fn(|cx| { s.waker.register(cx.waker()); - if r.events_end.read().bits() != 0 { - r.events_end.reset(); + if r.events_end().read() != 0 { + r.events_end().write_value(0); return Poll::Ready(()); } Poll::Pending }) .await; - let n_rx = r.rxd.amount.read().bits() as usize; - let n_tx = r.txd.amount.read().bits() as usize; + let n_rx = r.rxd().amount().read().0 as usize; + let n_tx = r.txd().amount().read().0 as usize; compiler_fence(Ordering::SeqCst); @@ -428,12 +420,12 @@ impl<'d, T: Instance> Spis<'d, T> { /// Checks if last transaction overread. pub fn is_overread(&mut self) -> bool { - T::regs().status.read().overread().is_present() + T::regs().status().read().overread() } /// Checks if last transaction overflowed. pub fn is_overflow(&mut self) -> bool { - T::regs().status.read().overflow().is_present() + T::regs().status().read().overflow() } } @@ -443,12 +435,12 @@ impl<'d, T: Instance> Drop for Spis<'d, T> { // Disable let r = T::regs(); - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); - gpio::deconfigure_pin(r.psel.sck.read().bits()); - gpio::deconfigure_pin(r.psel.csn.read().bits()); - gpio::deconfigure_pin(r.psel.miso.read().bits()); - gpio::deconfigure_pin(r.psel.mosi.read().bits()); + gpio::deconfigure_pin(r.psel().sck().read()); + gpio::deconfigure_pin(r.psel().csn().read()); + gpio::deconfigure_pin(r.psel().miso().read()); + gpio::deconfigure_pin(r.psel().mosi().read()); trace!("spis drop: done"); } @@ -467,13 +459,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::spis0::RegisterBlock; + fn regs() -> pac::spis::Spis; fn state() -> &'static State; } /// SPIS peripheral instance #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static { +pub trait Instance: SealedInstance + PeripheralType + 'static { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -481,8 +473,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static { macro_rules! impl_spis { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::spis::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::spis0::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::spis::Spis { + pac::$pac_type } fn state() -> &'static crate::spis::State { static STATE: crate::spis::State = crate::spis::State::new(); @@ -504,44 +496,39 @@ impl<'d, T: Instance> SetConfig for Spis<'d, T> { let r = T::regs(); // Configure mode. let mode = config.mode; - r.config.write(|w| { + r.config().write(|w| { + w.set_order(config.bit_order); match mode { MODE_0 => { - w.order().variant(config.bit_order); - w.cpol().active_high(); - w.cpha().leading(); + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::LEADING); } MODE_1 => { - w.order().variant(config.bit_order); - w.cpol().active_high(); - w.cpha().trailing(); + w.set_cpol(vals::Cpol::ACTIVE_HIGH); + w.set_cpha(vals::Cpha::TRAILING); } MODE_2 => { - w.order().variant(config.bit_order); - w.cpol().active_low(); - w.cpha().leading(); + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::LEADING); } MODE_3 => { - w.order().variant(config.bit_order); - w.cpol().active_low(); - w.cpha().trailing(); + w.set_cpol(vals::Cpol::ACTIVE_LOW); + w.set_cpha(vals::Cpha::TRAILING); } } - - w }); // Set over-read character. let orc = config.orc; - r.orc.write(|w| unsafe { w.orc().bits(orc) }); + r.orc().write(|w| w.set_orc(orc)); // Set default character. let def = config.def; - r.def.write(|w| unsafe { w.def().bits(def) }); + r.def().write(|w| w.set_def(def)); // Configure auto-acquire on 'transfer end' event. let auto_acquire = config.auto_acquire; - r.shorts.write(|w| w.end_acquire().bit(auto_acquire)); + r.shorts().write(|w| w.set_end_acquire(auto_acquire)); Ok(()) } diff --git a/embassy-nrf/src/temp.rs b/embassy-nrf/src/temp.rs index ed4a47713..44be0f6d1 100644 --- a/embassy-nrf/src/temp.rs +++ b/embassy-nrf/src/temp.rs @@ -4,13 +4,12 @@ use core::future::poll_fn; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::I30F2; use crate::interrupt::InterruptExt; use crate::peripherals::TEMP; -use crate::{interrupt, pac, Peripheral}; +use crate::{interrupt, pac, Peri}; /// Interrupt handler. pub struct InterruptHandler { @@ -19,15 +18,15 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let r = unsafe { &*pac::TEMP::PTR }; - r.intenclr.write(|w| w.datardy().clear()); + let r = pac::TEMP; + r.intenclr().write(|w| w.set_datardy(true)); WAKER.wake(); } } /// Builtin temperature sensor driver. pub struct Temp<'d> { - _peri: PeripheralRef<'d, TEMP>, + _peri: Peri<'d, TEMP>, } static WAKER: AtomicWaker = AtomicWaker::new(); @@ -35,11 +34,9 @@ static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d> Temp<'d> { /// Create a new temperature sensor driver. pub fn new( - _peri: impl Peripheral

+ 'd, + _peri: Peri<'d, TEMP>, _irq: impl interrupt::typelevel::Binding + 'd, ) -> Self { - into_ref!(_peri); - // Enable interrupt that signals temperature values interrupt::TEMP.unpend(); unsafe { interrupt::TEMP.enable() }; @@ -72,21 +69,21 @@ impl<'d> Temp<'d> { // In case the future is dropped, stop the task and reset events. let on_drop = OnDrop::new(|| { let t = Self::regs(); - t.tasks_stop.write(|w| unsafe { w.bits(1) }); - t.events_datardy.reset(); + t.tasks_stop().write_value(1); + t.events_datardy().write_value(0); }); let t = Self::regs(); - t.intenset.write(|w| w.datardy().set()); - unsafe { t.tasks_start.write(|w| w.bits(1)) }; + t.intenset().write(|w| w.set_datardy(true)); + t.tasks_start().write_value(1); let value = poll_fn(|cx| { WAKER.register(cx.waker()); - if t.events_datardy.read().bits() == 0 { + if t.events_datardy().read() == 0 { Poll::Pending } else { - t.events_datardy.reset(); - let raw = t.temp.read().bits(); + t.events_datardy().write_value(0); + let raw = t.temp().read(); Poll::Ready(I30F2::from_bits(raw as i32)) } }) @@ -95,7 +92,7 @@ impl<'d> Temp<'d> { value } - fn regs() -> &'static pac::temp::RegisterBlock { - unsafe { &*pac::TEMP::ptr() } + fn regs() -> pac::temp::Temp { + pac::TEMP } } diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index 3407c9504..03f4c2e2b 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -1,17 +1,22 @@ -use core::cell::Cell; -use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; -use core::{mem, ptr}; +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use critical_section::CriticalSection; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; -use embassy_time_driver::{AlarmHandle, Driver}; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; use crate::interrupt::InterruptExt; use crate::{interrupt, pac}; -fn rtc() -> &'static pac::rtc0::RegisterBlock { - unsafe { &*pac::RTC1::ptr() } +#[cfg(feature = "_nrf54l")] +fn rtc() -> pac::rtc::Rtc { + pac::RTC30 +} +#[cfg(not(feature = "_nrf54l"))] +fn rtc() -> pac::rtc::Rtc { + pac::RTC1 } /// Calculate the timestamp from the period count and the tick count. @@ -89,11 +94,6 @@ mod test { struct AlarmState { timestamp: Cell, - - // This is really a Option<(fn(*mut ()), *mut ())> - // but fn pointers aren't allowed in const yet - callback: Cell<*const ()>, - ctx: Cell<*mut ()>, } unsafe impl Send for AlarmState {} @@ -102,69 +102,70 @@ impl AlarmState { const fn new() -> Self { Self { timestamp: Cell::new(u64::MAX), - callback: Cell::new(ptr::null()), - ctx: Cell::new(ptr::null_mut()), } } } -const ALARM_COUNT: usize = 3; - struct RtcDriver { /// Number of 2^23 periods elapsed since boot. period: AtomicU32, - alarm_count: AtomicU8, /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. - alarms: Mutex<[AlarmState; ALARM_COUNT]>, + alarms: Mutex, + queue: Mutex>, } -const ALARM_STATE_NEW: AlarmState = AlarmState::new(); embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { period: AtomicU32::new(0), - alarm_count: AtomicU8::new(0), - alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), + queue: Mutex::new(RefCell::new(Queue::new())), }); impl RtcDriver { fn init(&'static self, irq_prio: crate::interrupt::Priority) { let r = rtc(); - r.cc[3].write(|w| unsafe { w.bits(0x800000) }); + r.cc(3).write(|w| w.set_compare(0x800000)); - r.intenset.write(|w| { - let w = w.ovrflw().set(); - let w = w.compare3().set(); - w + r.intenset().write(|w| { + w.set_ovrflw(true); + w.set_compare(3, true); }); - r.tasks_clear.write(|w| unsafe { w.bits(1) }); - r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.tasks_clear().write_value(1); + r.tasks_start().write_value(1); // Wait for clear - while r.counter.read().bits() != 0 {} + while r.counter().read().0 != 0 {} - interrupt::RTC1.set_priority(irq_prio); - unsafe { interrupt::RTC1.enable() }; + #[cfg(feature = "_nrf54l")] + { + interrupt::RTC30.set_priority(irq_prio); + unsafe { interrupt::RTC30.enable() }; + } + #[cfg(not(feature = "_nrf54l"))] + { + interrupt::RTC1.set_priority(irq_prio); + unsafe { interrupt::RTC1.enable() }; + } } fn on_interrupt(&self) { let r = rtc(); - if r.events_ovrflw.read().bits() == 1 { - r.events_ovrflw.write(|w| w); + if r.events_ovrflw().read() == 1 { + r.events_ovrflw().write_value(0); self.next_period(); } - if r.events_compare[3].read().bits() == 1 { - r.events_compare[3].write(|w| w); + if r.events_compare(3).read() == 1 { + r.events_compare(3).write_value(0); self.next_period(); } - for n in 0..ALARM_COUNT { - if r.events_compare[n].read().bits() == 1 { - r.events_compare[n].write(|w| w); - critical_section::with(|cs| { - self.trigger_alarm(n, cs); - }) - } + let n = 0; + if r.events_compare(n).read() == 1 { + r.events_compare(n).write_value(0); + critical_section::with(|cs| { + self.trigger_alarm(cs); + }); } } @@ -175,84 +176,45 @@ impl RtcDriver { self.period.store(period, Ordering::Relaxed); let t = (period as u64) << 23; - for n in 0..ALARM_COUNT { - let alarm = &self.alarms.borrow(cs)[n]; - let at = alarm.timestamp.get(); + let n = 0; + let alarm = &self.alarms.borrow(cs); + let at = alarm.timestamp.get(); - if at < t + 0xc00000 { - // just enable it. `set_alarm` has already set the correct CC val. - r.intenset.write(|w| unsafe { w.bits(compare_n(n)) }); - } + if at < t + 0xc00000 { + // just enable it. `set_alarm` has already set the correct CC val. + r.intenset().write(|w| w.0 = compare_n(n)); } }) } - fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { - // safety: we're allowed to assume the AlarmState is created by us, and - // we never create one that's out of bounds. - unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) } - } - - fn trigger_alarm(&self, n: usize, cs: CriticalSection) { + fn trigger_alarm(&self, cs: CriticalSection) { + let n = 0; let r = rtc(); - r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); + r.intenclr().write(|w| w.0 = compare_n(n)); - let alarm = &self.alarms.borrow(cs)[n]; + let alarm = &self.alarms.borrow(cs); alarm.timestamp.set(u64::MAX); // Call after clearing alarm, so the callback can set another alarm. - - // safety: - // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. - // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; - f(alarm.ctx.get()); - } -} - -impl Driver for RtcDriver { - fn now(&self) -> u64 { - // `period` MUST be read before `counter`, see comment at the top for details. - let period = self.period.load(Ordering::Relaxed); - compiler_fence(Ordering::Acquire); - let counter = rtc().counter.read().bits(); - calc_now(period, counter) + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } } - unsafe fn allocate_alarm(&self) -> Option { - 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 - } - }) - } + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let n = 0; + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - critical_section::with(|cs| { - let alarm = self.get_alarm(cs, alarm); - - alarm.callback.set(callback as *const ()); - alarm.ctx.set(ctx); - }) - } - - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { - critical_section::with(|cs| { - let n = alarm.id() as _; - let alarm = self.get_alarm(cs, alarm); - alarm.timestamp.set(timestamp); - - let r = rtc(); + let r = rtc(); + loop { let t = self.now(); if timestamp <= t { // If alarm timestamp has passed the alarm will not fire. // Disarm the alarm and return `false` to indicate that. - r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); + r.intenclr().write(|w| w.0 = compare_n(n)); alarm.timestamp.set(u64::MAX); @@ -269,30 +231,66 @@ impl Driver for RtcDriver { // To workaround this, we never write a timestamp smaller than N+3. // N+2 is not safe because rtc can tick from N to N+1 between calling now() and writing cc. // - // It is impossible for rtc to tick more than once because - // - this code takes less time than 1 tick - // - it runs with interrupts disabled so nothing else can preempt it. + // Since the critical section does not guarantee that a higher prio interrupt causes + // this to be delayed, we need to re-check how much time actually passed after setting the + // alarm, and retry if we are within the unsafe interval still. // // This means that an alarm can be delayed for up to 2 ticks (from t+1 to t+3), but this is allowed // by the Alarm trait contract. What's not allowed is triggering alarms *before* their scheduled time, // and we don't do that here. let safe_timestamp = timestamp.max(t + 3); - r.cc[n].write(|w| unsafe { w.bits(safe_timestamp as u32 & 0xFFFFFF) }); + r.cc(n).write(|w| w.set_compare(safe_timestamp as u32 & 0xFFFFFF)); let diff = timestamp - t; if diff < 0xc00000 { - r.intenset.write(|w| unsafe { w.bits(compare_n(n)) }); + r.intenset().write(|w| w.0 = compare_n(n)); + + // If we have not passed the timestamp, we can be sure the alarm will be invoked. Otherwise, + // we need to retry setting the alarm. + if self.now() + 2 <= timestamp { + return true; + } } else { // If it's too far in the future, don't setup the compare channel yet. // It will be setup later by `next_period`. - r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); + r.intenclr().write(|w| w.0 = compare_n(n)); + return true; } + } + } +} - true +impl Driver for RtcDriver { + fn now(&self) -> u64 { + // `period` MUST be read before `counter`, see comment at the top for details. + let period = self.period.load(Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + let counter = rtc().counter().read().0; + calc_now(period, counter) + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } }) } } +#[cfg(feature = "_nrf54l")] +#[cfg(feature = "rt")] +#[interrupt] +fn RTC30() { + DRIVER.on_interrupt() +} + +#[cfg(not(feature = "_nrf54l"))] #[cfg(feature = "rt")] #[interrupt] fn RTC1() { diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs index ac5328ded..5b58b0a50 100644 --- a/embassy-nrf/src/timer.rs +++ b/embassy-nrf/src/timer.rs @@ -6,20 +6,21 @@ #![macro_use] -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; +use crate::pac; +use crate::pac::timer::vals; use crate::ppi::{Event, Task}; -use crate::{pac, Peripheral}; pub(crate) trait SealedInstance { /// The number of CC registers this instance has. const CCS: usize; - fn regs() -> &'static pac::timer0::RegisterBlock; + fn regs() -> pac::timer::Timer; } /// Basic Timer instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: crate::interrupt::typelevel::Interrupt; } @@ -31,8 +32,8 @@ macro_rules! impl_timer { ($type:ident, $pac_type:ident, $irq:ident, $ccs:literal) => { impl crate::timer::SealedInstance for peripherals::$type { const CCS: usize = $ccs; - fn regs() -> &'static pac::timer0::RegisterBlock { - unsafe { &*(pac::$pac_type::ptr() as *const pac::timer0::RegisterBlock) } + fn regs() -> pac::timer::Timer { + unsafe { pac::timer::Timer::from_ptr(pac::$pac_type.as_ptr()) } } } impl crate::timer::Instance for peripherals::$type { @@ -83,7 +84,7 @@ pub enum Frequency { /// Timer driver. pub struct Timer<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } impl<'d, T: Instance> Timer<'d, T> { @@ -91,7 +92,7 @@ impl<'d, T: Instance> Timer<'d, T> { /// /// This can be useful for triggering tasks via PPI /// `Uarte` uses this internally. - pub fn new(timer: impl Peripheral

+ 'd) -> Self { + pub fn new(timer: Peri<'d, T>) -> Self { Self::new_inner(timer, false) } @@ -99,13 +100,11 @@ impl<'d, T: Instance> Timer<'d, T> { /// /// This can be useful for triggering tasks via PPI /// `Uarte` uses this internally. - pub fn new_counter(timer: impl Peripheral

+ 'd) -> Self { + pub fn new_counter(timer: Peri<'d, T>) -> Self { Self::new_inner(timer, true) } - fn new_inner(timer: impl Peripheral

+ 'd, _is_counter: bool) -> Self { - into_ref!(timer); - + fn new_inner(timer: Peri<'d, T>, _is_counter: bool) -> Self { let regs = T::regs(); let this = Self { _p: timer }; @@ -114,19 +113,19 @@ impl<'d, T: Instance> Timer<'d, T> { // since changing BITMODE while running can cause 'unpredictable behaviour' according to the specification. this.stop(); - #[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()); + regs.mode().write(|w| { + w.set_mode(match _is_counter { + #[cfg(not(feature = "_nrf51"))] + true => vals::Mode::LOW_POWER_COUNTER, + #[cfg(feature = "_nrf51")] + true => vals::Mode::COUNTER, + false => vals::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()); + regs.bitmode().write(|w| w.set_bitmode(vals::Bitmode::_32BIT)); // Initialize the counter at 0. this.clear(); @@ -148,38 +147,38 @@ impl<'d, T: Instance> Timer<'d, T> { /// Starts the timer. pub fn start(&self) { - T::regs().tasks_start.write(|w| unsafe { w.bits(1) }) + T::regs().tasks_start().write_value(1) } /// Stops the timer. pub fn stop(&self) { - T::regs().tasks_stop.write(|w| unsafe { w.bits(1) }) + T::regs().tasks_stop().write_value(1) } /// Reset the timer's counter to 0. pub fn clear(&self) { - T::regs().tasks_clear.write(|w| unsafe { w.bits(1) }) + T::regs().tasks_clear().write_value(1) } /// Returns the START task, for use with PPI. /// /// When triggered, this task starts the timer. pub fn task_start(&self) -> Task<'d> { - Task::from_reg(&T::regs().tasks_start) + Task::from_reg(T::regs().tasks_start()) } /// Returns the STOP task, for use with PPI. /// /// When triggered, this task stops the timer. pub fn task_stop(&self) -> Task<'d> { - Task::from_reg(&T::regs().tasks_stop) + Task::from_reg(T::regs().tasks_stop()) } /// Returns the CLEAR task, for use with PPI. /// /// When triggered, this task resets the timer's counter to 0. pub fn task_clear(&self) -> Task<'d> { - Task::from_reg(&T::regs().tasks_clear) + Task::from_reg(T::regs().tasks_clear()) } /// Returns the COUNT task, for use with PPI. @@ -187,7 +186,7 @@ impl<'d, T: Instance> Timer<'d, T> { /// When triggered, this task increments the timer's counter by 1. /// Only works in counter mode. pub fn task_count(&self) -> Task<'d> { - Task::from_reg(&T::regs().tasks_count) + Task::from_reg(T::regs().tasks_count()) } /// Change the timer's frequency. @@ -198,10 +197,10 @@ impl<'d, T: Instance> Timer<'d, T> { self.stop(); T::regs() - .prescaler + .prescaler() // SAFETY: `frequency` is a variant of `Frequency`, // whose values are all in the range of 0-9 (the valid range of `prescaler`). - .write(|w| unsafe { w.prescaler().bits(frequency as u8) }) + .write(|w| w.set_prescaler(frequency as u8)) } /// Returns this timer's `n`th CC register. @@ -228,34 +227,25 @@ impl<'d, T: Instance> Timer<'d, T> { /// When the register's CAPTURE task is triggered, the timer will store the current value of its counter in the register pub struct Cc<'d, T: Instance> { n: usize, - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } impl<'d, T: Instance> Cc<'d, T> { /// Get the current value stored in the register. pub fn read(&self) -> u32 { - #[cfg(not(feature = "nrf51"))] - return T::regs().cc[self.n].read().cc().bits(); - - #[cfg(feature = "nrf51")] - return T::regs().cc[self.n].read().bits(); + return T::regs().cc(self.n).read(); } /// Set the value stored in the register. /// /// `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. - #[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) }); + T::regs().cc(self.n).write_value(value); } /// Capture the current value of the timer's counter in this register, and return it. pub fn capture(&self) -> u32 { - T::regs().tasks_capture[self.n].write(|w| unsafe { w.bits(1) }); + T::regs().tasks_capture(self.n).write_value(1); self.read() } @@ -263,14 +253,14 @@ impl<'d, T: Instance> Cc<'d, T> { /// /// When triggered, this task will capture the current value of the timer's counter in this register. pub fn task_capture(&self) -> Task<'d> { - Task::from_reg(&T::regs().tasks_capture) + Task::from_reg(T::regs().tasks_capture(self.n)) } /// Returns this CC register's COMPARE event, for use with PPI. /// /// This event will fire when the timer's counter reaches the value in this CC register. pub fn event_compare(&self) -> Event<'d> { - Event::from_reg(&T::regs().events_compare[self.n]) + Event::from_reg(T::regs().events_compare(self.n)) } /// Enable the shortcut between this CC register's COMPARE event and the timer's CLEAR task. @@ -279,16 +269,12 @@ impl<'d, T: Instance> Cc<'d, T> { /// /// So, when the timer's counter reaches the value stored in this register, the timer's counter will be reset to 0. pub fn short_compare_clear(&self) { - T::regs() - .shorts - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << self.n)) }) + T::regs().shorts().modify(|w| w.set_compare_clear(self.n, true)) } /// Disable the shortcut between this CC register's COMPARE event and the timer's CLEAR task. pub fn unshort_compare_clear(&self) { - T::regs() - .shorts - .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << self.n)) }) + T::regs().shorts().modify(|w| w.set_compare_clear(self.n, false)) } /// Enable the shortcut between this CC register's COMPARE event and the timer's STOP task. @@ -297,15 +283,11 @@ impl<'d, T: Instance> Cc<'d, T> { /// /// So, when the timer's counter reaches the value stored in this register, the timer will stop counting up. pub fn short_compare_stop(&self) { - T::regs() - .shorts - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << (8 + self.n))) }) + T::regs().shorts().modify(|w| w.set_compare_stop(self.n, true)) } /// Disable the shortcut between this CC register's COMPARE event and the timer's STOP task. pub fn unshort_compare_stop(&self) { - T::regs() - .shorts - .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << (8 + self.n))) }) + T::regs().shorts().modify(|w| w.set_compare_stop(self.n, false)) } } diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index c64743ecc..3d5e841d1 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -9,27 +9,20 @@ use core::sync::atomic::Ordering::SeqCst; use core::task::Poll; use embassy_embedded_hal::SetConfig; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; #[cfg(feature = "time")] use embassy_time::{Duration, Instant}; +use embedded_hal_1::i2c::Operation; +pub use pac::twim::vals::Frequency; -use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::chip::EASY_DMA_SIZE; use crate::gpio::Pin as GpioPin; use crate::interrupt::typelevel::Interrupt; -use crate::util::{slice_in_ram, slice_in_ram_or}; -use crate::{gpio, interrupt, pac, Peripheral}; - -/// TWI frequency -#[derive(Clone, Copy)] -pub enum Frequency { - /// 100 kbps - K100 = 26738688, - /// 250 kbps - K250 = 67108864, - /// 400 kbps - K400 = 104857600, -} +use crate::pac::gpio::vals as gpiovals; +use crate::pac::twim::vals; +use crate::util::slice_in_ram; +use crate::{gpio, interrupt, pac}; /// TWIM config. #[non_exhaustive] @@ -81,8 +74,8 @@ pub enum Error { Transmit, /// Data reception failed. Receive, - /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. - BufferNotInRAM, + /// The buffer is not in data RAM and is larger than the RAM buffer. It's most likely in flash, and nRF's DMA cannot access flash. + RAMBufferTooSmall, /// Didn't receive an ACK bit after the address byte. Address might be wrong, or the i2c device chip might not be connected properly. AddressNack, /// Didn't receive an ACK bit after a data byte. @@ -103,77 +96,86 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); - if r.events_stopped.read().bits() != 0 { + if r.events_suspended().read() != 0 { s.end_waker.wake(); - r.intenclr.write(|w| w.stopped().clear()); + r.intenclr().write(|w| w.set_suspended(true)); } - if r.events_error.read().bits() != 0 { + if r.events_stopped().read() != 0 { s.end_waker.wake(); - r.intenclr.write(|w| w.error().clear()); + r.intenclr().write(|w| w.set_stopped(true)); + } + if r.events_error().read() != 0 { + s.end_waker.wake(); + r.intenclr().write(|w| w.set_error(true)); } } } /// TWI driver. pub struct Twim<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, + tx_ram_buffer: &'d mut [u8], } impl<'d, T: Instance> Twim<'d, T> { /// Create a new TWI driver. + /// + /// `tx_ram_buffer` is required if any write operations will be performed with data that is not in RAM. + /// Usually this is static data that the compiler locates in flash instead of RAM. The `tx_ram_buffer` + /// needs to be at least as large as the largest write operation that will be executed with a buffer + /// that is not in RAM. If all write operations will be performed from RAM, an empty buffer (`&[]`) may + /// be used. pub fn new( - twim: impl Peripheral

+ 'd, + twim: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, + sda: Peri<'d, impl GpioPin>, + scl: Peri<'d, impl GpioPin>, config: Config, + tx_ram_buffer: &'d mut [u8], ) -> Self { - into_ref!(twim, sda, scl); - let r = T::regs(); // Configure pins sda.conf().write(|w| { - w.dir().input(); - w.input().connect(); - if config.sda_high_drive { - w.drive().h0d1(); - } else { - w.drive().s0d1(); - } + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.sda_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); if config.sda_pullup { - w.pull().pullup(); + w.set_pull(gpiovals::Pull::PULLUP); } - w }); scl.conf().write(|w| { - w.dir().input(); - w.input().connect(); - if config.scl_high_drive { - w.drive().h0d1(); - } else { - w.drive().s0d1(); + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.scl_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); + if config.sda_pullup { + w.set_pull(gpiovals::Pull::PULLUP); } - if config.scl_pullup { - w.pull().pullup(); - } - w }); // Select pins. - r.psel.sda.write(|w| unsafe { w.bits(sda.psel_bits()) }); - r.psel.scl.write(|w| unsafe { w.bits(scl.psel_bits()) }); + r.psel().sda().write_value(sda.psel_bits()); + r.psel().scl().write_value(scl.psel_bits()); // Enable TWIM instance. - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); - let mut twim = Self { _p: twim }; + let mut twim = Self { + _p: twim, + tx_ram_buffer, + }; // Apply runtime peripheral configuration Self::set_config(&mut twim, &config).unwrap(); // Disable all events interrupts - r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; @@ -183,7 +185,17 @@ impl<'d, T: Instance> Twim<'d, T> { /// Set TX buffer, checking that it is in RAM and has suitable length. unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + let buffer = if slice_in_ram(buffer) { + buffer + } else { + if buffer.len() > self.tx_ram_buffer.len() { + return Err(Error::RAMBufferTooSmall); + } + trace!("Copying TWIM tx buffer into RAM for DMA"); + let ram_buffer = &mut self.tx_ram_buffer[..buffer.len()]; + ram_buffer.copy_from_slice(buffer); + &*ram_buffer + }; if buffer.len() > EASY_DMA_SIZE { return Err(Error::TxBufferTooLong); @@ -191,22 +203,18 @@ impl<'d, T: Instance> Twim<'d, T> { let r = T::regs(); - r.txd.ptr.write(|w| - // We're giving the register a pointer to the stack. Since we're - // waiting for the I2C transaction to end before this stack pointer - // becomes invalid, there's nothing wrong here. - // - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - w.ptr().bits(buffer.as_ptr() as u32)); - r.txd.maxcnt.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.txd().ptr().write_value(buffer.as_ptr() as u32); + r.txd().maxcnt().write(|w| // We're giving it the length of the buffer, so no danger of // accessing invalid memory. We have verified that the length of the // buffer fits in an `u8`, so the cast to `u8` is also fine. // // The MAXCNT field is 8 bits wide and accepts the full range of // values. - w.maxcnt().bits(buffer.len() as _)); + w.set_maxcnt(buffer.len() as _)); Ok(()) } @@ -222,15 +230,11 @@ impl<'d, T: Instance> Twim<'d, T> { let r = T::regs(); - r.rxd.ptr.write(|w| - // We're giving the register a pointer to the stack. Since we're - // waiting for the I2C transaction to end before this stack pointer - // becomes invalid, there's nothing wrong here. - // - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - w.ptr().bits(buffer.as_mut_ptr() as u32)); - r.rxd.maxcnt.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.rxd().ptr().write_value(buffer.as_mut_ptr() as u32); + r.rxd().maxcnt().write(|w| // We're giving it the length of the buffer, so no danger of // accessing invalid memory. We have verified that the length of the // buffer fits in an `u8`, so the cast to the type of maxcnt @@ -240,29 +244,32 @@ impl<'d, T: Instance> Twim<'d, T> { // type than a u8, so we use a `_` cast rather than a `u8` cast. // The MAXCNT field is thus at least 8 bits wide and accepts the // full range of values that fit in a `u8`. - w.maxcnt().bits(buffer.len() as _)); + w.set_maxcnt(buffer.len() as _)); Ok(()) } fn clear_errorsrc(&mut self) { let r = T::regs(); - r.errorsrc - .write(|w| w.anack().bit(true).dnack().bit(true).overrun().bit(true)); + r.errorsrc().write(|w| { + w.set_anack(true); + w.set_dnack(true); + w.set_overrun(true); + }); } /// Get Error instance, if any occurred. - fn check_errorsrc(&self) -> Result<(), Error> { + fn check_errorsrc() -> Result<(), Error> { let r = T::regs(); - let err = r.errorsrc.read(); - if err.anack().is_received() { + let err = r.errorsrc().read(); + if err.anack() { return Err(Error::AddressNack); } - if err.dnack().is_received() { + if err.dnack() { return Err(Error::DataNack); } - if err.overrun().is_received() { + if err.overrun() { return Err(Error::Overrun); } Ok(()) @@ -270,7 +277,7 @@ impl<'d, T: Instance> Twim<'d, T> { fn check_rx(&self, len: usize) -> Result<(), Error> { let r = T::regs(); - if r.rxd.amount.read().bits() != len as u32 { + if r.rxd().amount().read().0 != len as u32 { Err(Error::Receive) } else { Ok(()) @@ -279,7 +286,7 @@ impl<'d, T: Instance> Twim<'d, T> { fn check_tx(&self, len: usize) -> Result<(), Error> { let r = T::regs(); - if r.txd.amount.read().bits() != len as u32 { + if r.txd().amount().read().0 != len as u32 { Err(Error::Transmit) } else { Ok(()) @@ -290,13 +297,14 @@ impl<'d, T: Instance> Twim<'d, T> { fn blocking_wait(&mut self) { let r = T::regs(); loop { - if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + if r.events_suspended().read() != 0 || r.events_stopped().read() != 0 { + r.events_suspended().write_value(0); + r.events_stopped().write_value(0); break; } - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); } } } @@ -307,16 +315,16 @@ impl<'d, T: Instance> Twim<'d, T> { let r = T::regs(); let deadline = Instant::now() + timeout; loop { - if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + if r.events_suspended().read() != 0 || r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); break; } - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); } if Instant::now() > deadline { - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.tasks_stop().write_value(1); return Err(Error::Timeout); } } @@ -325,190 +333,282 @@ impl<'d, T: Instance> Twim<'d, T> { } /// Wait for stop or error - fn async_wait(&mut self) -> impl Future { + fn async_wait(&mut self) -> impl Future> { poll_fn(move |cx| { let r = T::regs(); let s = T::state(); s.end_waker.register(cx.waker()); - if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + if r.events_suspended().read() != 0 || r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); - return Poll::Ready(()); + return Poll::Ready(Ok(())); } // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + if let Err(e) = Self::check_errorsrc() { + return Poll::Ready(Err(e)); + } else { + panic!("Found events_error bit without an error in errorsrc reg"); + } } Poll::Pending }) } - fn setup_write_from_ram(&mut self, address: u8, buffer: &[u8], inten: bool) -> Result<(), Error> { - let r = T::regs(); - - compiler_fence(SeqCst); - - r.address.write(|w| unsafe { w.address().bits(address) }); - - // Set up the DMA write. - unsafe { self.set_tx_buffer(buffer)? }; - - // Clear events - r.events_stopped.reset(); - r.events_error.reset(); - r.events_lasttx.reset(); - self.clear_errorsrc(); - - if inten { - r.intenset.write(|w| w.stopped().set().error().set()); - } else { - r.intenclr.write(|w| w.stopped().clear().error().clear()); - } - - // Start write operation. - r.shorts.write(|w| w.lasttx_stop().enabled()); - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - 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) }); - } - Ok(()) - } - - fn setup_read(&mut self, address: u8, buffer: &mut [u8], inten: bool) -> Result<(), Error> { - let r = T::regs(); - - compiler_fence(SeqCst); - - r.address.write(|w| unsafe { w.address().bits(address) }); - - // Set up the DMA read. - unsafe { self.set_rx_buffer(buffer)? }; - - // Clear events - r.events_stopped.reset(); - r.events_error.reset(); - self.clear_errorsrc(); - - if inten { - r.intenset.write(|w| w.stopped().set().error().set()); - } else { - r.intenclr.write(|w| w.stopped().clear().error().clear()); - } - - // Start read operation. - r.shorts.write(|w| w.lastrx_stop().enabled()); - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - 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) }); - } - Ok(()) - } - - fn setup_write_read_from_ram( + fn setup_operations( &mut self, address: u8, - wr_buffer: &[u8], - rd_buffer: &mut [u8], + operations: &mut [Operation<'_>], + last_op: Option<&Operation<'_>>, inten: bool, - ) -> Result<(), Error> { + ) -> Result { let r = T::regs(); compiler_fence(SeqCst); - r.address.write(|w| unsafe { w.address().bits(address) }); + r.address().write(|w| w.set_address(address)); - // Set up DMA buffers. - unsafe { - self.set_tx_buffer(wr_buffer)?; - self.set_rx_buffer(rd_buffer)?; - } - - // Clear events - r.events_stopped.reset(); - r.events_error.reset(); + r.events_suspended().write_value(0); + r.events_stopped().write_value(0); + r.events_error().write_value(0); self.clear_errorsrc(); if inten { - r.intenset.write(|w| w.stopped().set().error().set()); + r.intenset().write(|w| { + w.set_suspended(true); + w.set_stopped(true); + w.set_error(true); + }); } else { - r.intenclr.write(|w| w.stopped().clear().error().clear()); + r.intenclr().write(|w| { + w.set_suspended(true); + w.set_stopped(true); + w.set_error(true); + }); } - // Start write+read operation. - r.shorts.write(|w| { - w.lasttx_startrx().enabled(); - w.lastrx_stop().enabled(); - w - }); - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - 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) }); - } + assert!(!operations.is_empty()); + match operations { + [Operation::Read(_), Operation::Read(_), ..] => { + panic!("Consecutive read operations are not supported!") + } + [Operation::Read(rd_buffer), Operation::Write(wr_buffer), rest @ ..] => { + let stop = rest.is_empty(); + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(wr_buffer)?; + self.set_rx_buffer(rd_buffer)?; + } + + r.shorts().write(|w| { + w.set_lastrx_starttx(true); + if stop { + w.set_lasttx_stop(true); + } else { + w.set_lasttx_suspend(true); + } + }); + + // Start read+write operation. + r.tasks_startrx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + // TODO: Handle empty write buffer + if rd_buffer.is_empty() { + // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STARTTX ourselves. + r.tasks_starttx().write_value(1); + } + + Ok(2) + } + [Operation::Read(buffer)] => { + // Set up DMA buffers. + unsafe { + self.set_rx_buffer(buffer)?; + } + + r.shorts().write(|w| w.set_lastrx_stop(true)); + + // Start read operation. + r.tasks_startrx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + 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_value(1); + } + + Ok(1) + } + [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] + if !wr_buffer.is_empty() && !rd_buffer.is_empty() => + { + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(wr_buffer)?; + self.set_rx_buffer(rd_buffer)?; + } + + // Start write+read operation. + r.shorts().write(|w| { + w.set_lasttx_startrx(true); + w.set_lastrx_stop(true); + }); + + r.tasks_starttx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + Ok(2) + } + [Operation::Write(buffer), rest @ ..] => { + let stop = rest.is_empty(); + + // Set up DMA buffers. + unsafe { + self.set_tx_buffer(buffer)?; + } + + // Start write operation. + r.shorts().write(|w| { + if stop { + w.set_lasttx_stop(true); + } else { + w.set_lasttx_suspend(true); + } + }); + + r.tasks_starttx().write_value(1); + if last_op.is_some() { + r.tasks_resume().write_value(1); + } + + if buffer.is_empty() { + // With a zero-length buffer, LASTTX doesn't fire (because there's no last byte!), so do the STOP/SUSPEND ourselves. + if stop { + r.tasks_stop().write_value(1); + } else { + r.tasks_suspend().write_value(1); + } + } + + Ok(1) + } + [] => unreachable!(), + } + } + + fn check_operations(&mut self, operations: &[Operation<'_>]) -> Result<(), Error> { + compiler_fence(SeqCst); + Self::check_errorsrc()?; + + assert!(operations.len() == 1 || operations.len() == 2); + match operations { + [Operation::Read(rd_buffer), Operation::Write(wr_buffer)] + | [Operation::Write(wr_buffer), Operation::Read(rd_buffer)] => { + self.check_rx(rd_buffer.len())?; + self.check_tx(wr_buffer.len())?; + } + [Operation::Read(buffer)] => { + self.check_rx(buffer.len())?; + } + [Operation::Write(buffer), ..] => { + self.check_tx(buffer.len())?; + } + _ => unreachable!(), + } Ok(()) } - fn setup_write_read( - &mut self, - address: u8, - wr_buffer: &[u8], - rd_buffer: &mut [u8], - inten: bool, - ) -> Result<(), Error> { - match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) { - Ok(_) => Ok(()), - Err(Error::BufferNotInRAM) => { - trace!("Copying TWIM tx buffer into RAM for DMA"); - let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; - tx_ram_buf.copy_from_slice(wr_buffer); - self.setup_write_read_from_ram(address, tx_ram_buf, rd_buffer, inten) - } - Err(error) => Err(error), + // =========================================== + + /// Execute the provided operations on the I2C bus. + /// + /// Each buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// + /// Consecutive `Operation::Read`s are not supported due to hardware + /// limitations. + /// + /// An `Operation::Write` following an `Operation::Read` must have a + /// non-empty buffer. + pub fn blocking_transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.blocking_wait(); + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } + Ok(()) } - fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { - match self.setup_write_from_ram(address, wr_buffer, inten) { - Ok(_) => Ok(()), - Err(Error::BufferNotInRAM) => { - trace!("Copying TWIM tx buffer into RAM for DMA"); - let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; - tx_ram_buf.copy_from_slice(wr_buffer); - self.setup_write_from_ram(address, tx_ram_buf, inten) - } - Err(error) => Err(error), + /// Execute the provided operations on the I2C bus with timeout. + /// + /// See [`blocking_transaction`]. + #[cfg(feature = "time")] + pub fn blocking_transaction_timeout( + &mut self, + address: u8, + mut operations: &mut [Operation<'_>], + timeout: Duration, + ) -> Result<(), Error> { + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, last_op, false)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.blocking_wait_timeout(timeout)?; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; } + Ok(()) } + /// Execute the provided operations on the I2C bus. + /// + /// Each buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// + /// Consecutive `Operation::Read`s are not supported due to hardware + /// limitations. + /// + /// An `Operation::Write` following an `Operation::Read` must have a + /// non-empty buffer. + pub async fn transaction(&mut self, address: u8, mut operations: &mut [Operation<'_>]) -> Result<(), Error> { + let mut last_op = None; + while !operations.is_empty() { + let ops = self.setup_operations(address, operations, last_op, true)?; + let (in_progress, rest) = operations.split_at_mut(ops); + self.async_wait().await?; + self.check_operations(in_progress)?; + last_op = in_progress.last(); + operations = rest; + } + Ok(()) + } + + // =========================================== + /// Write to an I2C slave. /// /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write(address, buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) - } - - /// Same as [`blocking_write`](Twim::blocking_write) 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, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write_from_ram(address, buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.blocking_transaction(address, &mut [Operation::Write(buffer)]) } /// Read from an I2C slave. @@ -516,12 +616,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.setup_read(address, buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_rx(buffer.len())?; - Ok(()) + self.blocking_transaction(address, &mut [Operation::Read(buffer)]) } /// Write data to an I2C slave, then read data from the slave without @@ -530,29 +625,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffers must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub fn blocking_write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { - self.setup_write_read(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) - } - - /// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. - pub fn blocking_write_read_from_ram( - &mut self, - address: u8, - wr_buffer: &[u8], - rd_buffer: &mut [u8], - ) -> Result<(), Error> { - self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait(); - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.blocking_transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) } // =========================================== @@ -562,28 +635,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// See [`blocking_write`]. #[cfg(feature = "time")] pub fn blocking_write_timeout(&mut self, address: u8, buffer: &[u8], timeout: Duration) -> Result<(), Error> { - self.setup_write(address, buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) - } - - /// Same as [`blocking_write`](Twim::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. - #[cfg(feature = "time")] - pub fn blocking_write_from_ram_timeout( - &mut self, - address: u8, - buffer: &[u8], - timeout: Duration, - ) -> Result<(), Error> { - self.setup_write_from_ram(address, buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.blocking_transaction_timeout(address, &mut [Operation::Write(buffer)], timeout) } /// Read from an I2C slave. @@ -592,12 +644,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// and at most 65535 bytes on the nRF52840. #[cfg(feature = "time")] pub fn blocking_read_timeout(&mut self, address: u8, buffer: &mut [u8], timeout: Duration) -> Result<(), Error> { - self.setup_read(address, buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_rx(buffer.len())?; - Ok(()) + self.blocking_transaction_timeout(address, &mut [Operation::Read(buffer)], timeout) } /// Write data to an I2C slave, then read data from the slave without @@ -613,31 +660,11 @@ impl<'d, T: Instance> Twim<'d, T> { rd_buffer: &mut [u8], timeout: Duration, ) -> Result<(), Error> { - self.setup_write_read(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) - } - - /// Same as [`blocking_write_read`](Twim::blocking_write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. - #[cfg(feature = "time")] - pub fn blocking_write_read_from_ram_timeout( - &mut self, - address: u8, - wr_buffer: &[u8], - rd_buffer: &mut [u8], - timeout: Duration, - ) -> Result<(), Error> { - self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, false)?; - self.blocking_wait_timeout(timeout)?; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.blocking_transaction_timeout( + address, + &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)], + timeout, + ) } // =========================================== @@ -647,12 +674,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.setup_read(address, buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_rx(buffer.len())?; - Ok(()) + self.transaction(address, &mut [Operation::Read(buffer)]).await } /// Write to an I2C slave. @@ -660,22 +682,7 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffer must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write(address, buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) - } - - /// Same as [`write`](Twim::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, address: u8, buffer: &[u8]) -> Result<(), Error> { - self.setup_write_from_ram(address, buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(buffer.len())?; - Ok(()) + self.transaction(address, &mut [Operation::Write(buffer)]).await } /// Write data to an I2C slave, then read data from the slave without @@ -684,29 +691,8 @@ impl<'d, T: Instance> Twim<'d, T> { /// The buffers must have a length of at most 255 bytes on the nRF52832 /// and at most 65535 bytes on the nRF52840. pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { - self.setup_write_read(address, wr_buffer, rd_buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) - } - - /// Same as [`write_read`](Twim::write_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. - pub async fn write_read_from_ram( - &mut self, - address: u8, - wr_buffer: &[u8], - rd_buffer: &mut [u8], - ) -> Result<(), Error> { - self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, true)?; - self.async_wait().await; - compiler_fence(SeqCst); - self.check_errorsrc()?; - self.check_tx(wr_buffer.len())?; - self.check_rx(rd_buffer.len())?; - Ok(()) + self.transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)]) + .await } } @@ -718,10 +704,10 @@ impl<'a, T: Instance> Drop for Twim<'a, T> { // disable! let r = T::regs(); - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); - gpio::deconfigure_pin(r.psel.sda.read().bits()); - gpio::deconfigure_pin(r.psel.scl.read().bits()); + gpio::deconfigure_pin(r.psel().sda().read()); + gpio::deconfigure_pin(r.psel().scl().read()); trace!("twim drop: done"); } @@ -740,13 +726,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::twim0::RegisterBlock; + fn regs() -> pac::twim::Twim; fn state() -> &'static State; } /// TWIM peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static { +pub trait Instance: SealedInstance + PeripheralType + 'static { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -754,8 +740,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static { macro_rules! impl_twim { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::twim::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::twim0::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::twim::Twim { + pac::$pac_type } fn state() -> &'static crate::twim::State { static STATE: crate::twim::State = crate::twim::State::new(); @@ -777,16 +763,7 @@ mod eh02 { type Error = Error; fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { - if slice_in_ram(bytes) { - self.blocking_write(addr, bytes) - } else { - let buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..]; - for chunk in bytes.chunks(FORCE_COPY_BUFFER_SIZE) { - buf[..chunk.len()].copy_from_slice(chunk); - self.blocking_write(addr, &buf[..chunk.len()])?; - } - Ok(()) - } + self.blocking_write(addr, bytes) } } @@ -814,7 +791,7 @@ impl embedded_hal_1::i2c::Error for Error { Self::RxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other, Self::Transmit => embedded_hal_1::i2c::ErrorKind::Other, Self::Receive => embedded_hal_1::i2c::ErrorKind::Other, - Self::BufferNotInRAM => embedded_hal_1::i2c::ErrorKind::Other, + Self::RAMBufferTooSmall => embedded_hal_1::i2c::ErrorKind::Other, Self::AddressNack => { embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) } @@ -832,47 +809,14 @@ impl<'d, T: Instance> embedded_hal_1::i2c::ErrorType for Twim<'d, T> { } impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> { - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(address, buffer) - } - - fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(address, buffer) - } - - fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_write_read(address, wr_buffer, rd_buffer) - } - - fn transaction( - &mut self, - _address: u8, - _operations: &mut [embedded_hal_1::i2c::Operation<'_>], - ) -> Result<(), Self::Error> { - todo!(); + fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { + self.blocking_transaction(address, operations) } } impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { - async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { - self.read(address, read).await - } - - async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { - self.write(address, write).await - } - async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { - self.write_read(address, write, read).await - } - - async fn transaction( - &mut self, - address: u8, - operations: &mut [embedded_hal_1::i2c::Operation<'_>], - ) -> Result<(), Self::Error> { - let _ = address; - let _ = operations; - todo!() + async fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { + self.transaction(address, operations).await } } @@ -881,8 +825,7 @@ impl<'d, T: Instance> SetConfig for Twim<'d, T> { type ConfigError = (); fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { let r = T::regs(); - r.frequency - .write(|w| unsafe { w.frequency().bits(config.frequency as u32) }); + r.frequency().write(|w| w.set_frequency(config.frequency)); Ok(()) } diff --git a/embassy-nrf/src/twis.rs b/embassy-nrf/src/twis.rs index f3eab008f..3e4d537ae 100644 --- a/embassy-nrf/src/twis.rs +++ b/embassy-nrf/src/twis.rs @@ -8,7 +8,7 @@ use core::sync::atomic::compiler_fence; use core::sync::atomic::Ordering::SeqCst; use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; #[cfg(feature = "time")] use embassy_time::{Duration, Instant}; @@ -16,8 +16,10 @@ use embassy_time::{Duration, Instant}; use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; use crate::gpio::Pin as GpioPin; use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::twis::vals; use crate::util::slice_in_ram_or; -use crate::{gpio, interrupt, pac, Peripheral}; +use crate::{gpio, interrupt, pac}; /// TWIS config. #[non_exhaustive] @@ -119,90 +121,87 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); - if r.events_read.read().bits() != 0 || r.events_write.read().bits() != 0 { + if r.events_read().read() != 0 || r.events_write().read() != 0 { s.waker.wake(); - r.intenclr.modify(|_r, w| w.read().clear().write().clear()); + r.intenclr().write(|w| { + w.set_read(true); + w.set_write(true); + }); } - if r.events_stopped.read().bits() != 0 { + if r.events_stopped().read() != 0 { s.waker.wake(); - r.intenclr.modify(|_r, w| w.stopped().clear()); + r.intenclr().write(|w| w.set_stopped(true)); } - if r.events_error.read().bits() != 0 { + if r.events_error().read() != 0 { s.waker.wake(); - r.intenclr.modify(|_r, w| w.error().clear()); + r.intenclr().write(|w| w.set_error(true)); } } } /// TWIS driver. pub struct Twis<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } impl<'d, T: Instance> Twis<'d, T> { /// Create a new TWIS driver. pub fn new( - twis: impl Peripheral

+ 'd, + twis: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - sda: impl Peripheral

+ 'd, - scl: impl Peripheral

+ 'd, + sda: Peri<'d, impl GpioPin>, + scl: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(twis, sda, scl); - let r = T::regs(); // Configure pins sda.conf().write(|w| { - w.dir().input(); - w.input().connect(); - if config.sda_high_drive { - w.drive().h0d1(); - } else { - w.drive().s0d1(); - } + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.sda_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); if config.sda_pullup { - w.pull().pullup(); + w.set_pull(gpiovals::Pull::PULLUP); } - w }); scl.conf().write(|w| { - w.dir().input(); - w.input().connect(); - if config.scl_high_drive { - w.drive().h0d1(); - } else { - w.drive().s0d1(); + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(match config.scl_high_drive { + true => gpiovals::Drive::H0D1, + false => gpiovals::Drive::S0D1, + }); + if config.sda_pullup { + w.set_pull(gpiovals::Pull::PULLUP); } - if config.scl_pullup { - w.pull().pullup(); - } - w }); // Select pins. - r.psel.sda.write(|w| unsafe { w.bits(sda.psel_bits()) }); - r.psel.scl.write(|w| unsafe { w.bits(scl.psel_bits()) }); + r.psel().sda().write_value(sda.psel_bits()); + r.psel().scl().write_value(scl.psel_bits()); // Enable TWIS instance. - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); // Disable all events interrupts - r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); // Set address - r.address[0].write(|w| unsafe { w.address().bits(config.address0) }); - r.config.write(|w| w.address0().enabled()); + r.address(0).write(|w| w.set_address(config.address0)); + r.config().write(|w| w.set_address0(true)); if let Some(address1) = config.address1 { - r.address[1].write(|w| unsafe { w.address().bits(address1) }); - r.config.modify(|_r, w| w.address1().enabled()); + r.address(1).write(|w| w.set_address(address1)); + r.config().modify(|w| w.set_address1(true)); } // Set over-read character - r.orc.write(|w| unsafe { w.orc().bits(config.orc) }); + r.orc().write(|w| w.set_orc(config.orc)); // Generate suspend on read event - r.shorts.write(|w| w.read_suspend().enabled()); + r.shorts().write(|w| w.set_read_suspend(true)); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; @@ -220,22 +219,18 @@ impl<'d, T: Instance> Twis<'d, T> { let r = T::regs(); - r.txd.ptr.write(|w| - // We're giving the register a pointer to the stack. Since we're - // waiting for the I2C transaction to end before this stack pointer - // becomes invalid, there's nothing wrong here. - // - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - w.ptr().bits(buffer.as_ptr() as u32)); - r.txd.maxcnt.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.txd().ptr().write_value(buffer.as_ptr() as u32); + r.txd().maxcnt().write(|w| // We're giving it the length of the buffer, so no danger of // accessing invalid memory. We have verified that the length of the // buffer fits in an `u8`, so the cast to `u8` is also fine. // // The MAXCNT field is 8 bits wide and accepts the full range of // values. - w.maxcnt().bits(buffer.len() as _)); + w.set_maxcnt(buffer.len() as _)); Ok(()) } @@ -251,15 +246,11 @@ impl<'d, T: Instance> Twis<'d, T> { let r = T::regs(); - r.rxd.ptr.write(|w| - // We're giving the register a pointer to the stack. Since we're - // waiting for the I2C transaction to end before this stack pointer - // becomes invalid, there's nothing wrong here. - // - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - w.ptr().bits(buffer.as_mut_ptr() as u32)); - r.rxd.maxcnt.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + r.rxd().ptr().write_value(buffer.as_mut_ptr() as u32); + r.rxd().maxcnt().write(|w| // We're giving it the length of the buffer, so no danger of // accessing invalid memory. We have verified that the length of the // buffer fits in an `u8`, so the cast to the type of maxcnt @@ -269,48 +260,51 @@ impl<'d, T: Instance> Twis<'d, T> { // type than a u8, so we use a `_` cast rather than a `u8` cast. // The MAXCNT field is thus at least 8 bits wide and accepts the // full range of values that fit in a `u8`. - w.maxcnt().bits(buffer.len() as _)); + w.set_maxcnt(buffer.len() as _)); Ok(()) } fn clear_errorsrc(&mut self) { let r = T::regs(); - r.errorsrc - .write(|w| w.overflow().bit(true).overread().bit(true).dnack().bit(true)); + r.errorsrc().write(|w| { + w.set_overflow(true); + w.set_overread(true); + w.set_dnack(true); + }); } /// Returns matched address for latest command. pub fn address_match(&self) -> u8 { let r = T::regs(); - r.address[r.match_.read().bits() as usize].read().address().bits() + r.address(r.match_().read().0 as usize).read().address() } /// Returns the index of the address matched in the latest command. pub fn address_match_index(&self) -> usize { - T::regs().match_.read().bits() as _ + T::regs().match_().read().0 as _ } /// Wait for read, write, stop or error fn blocking_listen_wait(&mut self) -> Result { let r = T::regs(); loop { - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - while r.events_stopped.read().bits() == 0 {} + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + while r.events_stopped().read() == 0 {} return Err(Error::Overflow); } - if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); return Err(Error::Bus); } - if r.events_read.read().bits() != 0 { - r.events_read.reset(); + if r.events_read().read() != 0 { + r.events_read().write_value(0); return Ok(Status::Read); } - if r.events_write.read().bits() != 0 { - r.events_write.reset(); + if r.events_write().read() != 0 { + r.events_write().write_value(0); return Ok(Status::Write); } } @@ -321,22 +315,22 @@ impl<'d, T: Instance> Twis<'d, T> { let r = T::regs(); loop { // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); return Err(Error::Overflow); - } else if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); return match status { Status::Read => Ok(Command::Read), Status::Write => { - let n = r.rxd.amount.read().bits() as usize; + let n = r.rxd().amount().read().0 as usize; Ok(Command::Write(n)) } }; - } else if r.events_read.read().bits() != 0 { - r.events_read.reset(); - let n = r.rxd.amount.read().bits() as usize; + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); + let n = r.rxd().amount().read().0 as usize; return Ok(Command::WriteRead(n)); } } @@ -347,20 +341,20 @@ impl<'d, T: Instance> Twis<'d, T> { let r = T::regs(); loop { // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - let errorsrc = r.errorsrc.read(); - if errorsrc.overread().is_detected() { + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + let errorsrc = r.errorsrc().read(); + if errorsrc.overread() { return Err(Error::OverRead); - } else if errorsrc.dnack().is_received() { + } else if errorsrc.dnack() { return Err(Error::DataNack); } else { return Err(Error::Bus); } - } else if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); - let n = r.txd.amount.read().bits() as usize; + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + let n = r.txd().amount().read().0 as usize; return Ok(n); } } @@ -373,23 +367,23 @@ impl<'d, T: Instance> Twis<'d, T> { let deadline = Instant::now() + timeout; loop { // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - let errorsrc = r.errorsrc.read(); - if errorsrc.overread().is_detected() { + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + let errorsrc = r.errorsrc().read(); + if errorsrc.overread() { return Err(Error::OverRead); - } else if errorsrc.dnack().is_received() { + } else if errorsrc.dnack() { return Err(Error::DataNack); } else { return Err(Error::Bus); } - } else if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); - let n = r.txd.amount.read().bits() as usize; + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + let n = r.txd().amount().read().0 as usize; return Ok(n); } else if Instant::now() > deadline { - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.tasks_stop().write_value(1); return Err(Error::Timeout); } } @@ -401,26 +395,26 @@ impl<'d, T: Instance> Twis<'d, T> { let r = T::regs(); let deadline = Instant::now() + timeout; loop { - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - while r.events_stopped.read().bits() == 0 {} + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + while r.events_stopped().read() == 0 {} return Err(Error::Overflow); } - if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); return Err(Error::Bus); } - if r.events_read.read().bits() != 0 { - r.events_read.reset(); + if r.events_read().read() != 0 { + r.events_read().write_value(0); return Ok(Status::Read); } - if r.events_write.read().bits() != 0 { - r.events_write.reset(); + if r.events_write().read() != 0 { + r.events_write().write_value(0); return Ok(Status::Write); } if Instant::now() > deadline { - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.tasks_stop().write_value(1); return Err(Error::Timeout); } } @@ -433,25 +427,25 @@ impl<'d, T: Instance> Twis<'d, T> { let deadline = Instant::now() + timeout; loop { // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); return Err(Error::Overflow); - } else if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); return match status { Status::Read => Ok(Command::Read), Status::Write => { - let n = r.rxd.amount.read().bits() as usize; + let n = r.rxd().amount().read().0 as usize; Ok(Command::Write(n)) } }; - } else if r.events_read.read().bits() != 0 { - r.events_read.reset(); - let n = r.rxd.amount.read().bits() as usize; + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); + let n = r.rxd().amount().read().0 as usize; return Ok(Command::WriteRead(n)); } else if Instant::now() > deadline { - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.tasks_stop().write_value(1); return Err(Error::Timeout); } } @@ -466,20 +460,20 @@ impl<'d, T: Instance> Twis<'d, T> { s.waker.register(cx.waker()); // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); - let errorsrc = r.errorsrc.read(); - if errorsrc.overread().is_detected() { + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); + let errorsrc = r.errorsrc().read(); + if errorsrc.overread() { return Poll::Ready(Err(Error::OverRead)); - } else if errorsrc.dnack().is_received() { + } else if errorsrc.dnack() { return Poll::Ready(Err(Error::DataNack)); } else { return Poll::Ready(Err(Error::Bus)); } - } else if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); - let n = r.txd.amount.read().bits() as usize; + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); + let n = r.txd().amount().read().0 as usize; return Poll::Ready(Ok(n)); } @@ -496,18 +490,18 @@ impl<'d, T: Instance> Twis<'d, T> { s.waker.register(cx.waker()); // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); return Poll::Ready(Err(Error::Overflow)); - } else if r.events_read.read().bits() != 0 { - r.events_read.reset(); + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); return Poll::Ready(Ok(Status::Read)); - } else if r.events_write.read().bits() != 0 { - r.events_write.reset(); + } else if r.events_write().read() != 0 { + r.events_write().write_value(0); return Poll::Ready(Ok(Status::Write)); - } else if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); return Poll::Ready(Err(Error::Bus)); } Poll::Pending @@ -523,22 +517,22 @@ impl<'d, T: Instance> Twis<'d, T> { s.waker.register(cx.waker()); // stop if an error occurred - if r.events_error.read().bits() != 0 { - r.events_error.reset(); - r.tasks_stop.write(|w| unsafe { w.bits(1) }); + if r.events_error().read() != 0 { + r.events_error().write_value(0); + r.tasks_stop().write_value(1); return Poll::Ready(Err(Error::Overflow)); - } else if r.events_stopped.read().bits() != 0 { - r.events_stopped.reset(); + } else if r.events_stopped().read() != 0 { + r.events_stopped().write_value(0); return match status { Status::Read => Poll::Ready(Ok(Command::Read)), Status::Write => { - let n = r.rxd.amount.read().bits() as usize; + let n = r.rxd().amount().read().0 as usize; Poll::Ready(Ok(Command::Write(n))) } }; - } else if r.events_read.read().bits() != 0 { - r.events_read.reset(); - let n = r.rxd.amount.read().bits() as usize; + } else if r.events_read().read() != 0 { + r.events_read().write_value(0); + let n = r.rxd().amount().read().0 as usize; return Poll::Ready(Ok(Command::WriteRead(n))); } Poll::Pending @@ -554,19 +548,25 @@ impl<'d, T: Instance> Twis<'d, T> { unsafe { self.set_tx_buffer(buffer)? }; // Clear events - r.events_stopped.reset(); - r.events_error.reset(); + r.events_stopped().write_value(0); + r.events_error().write_value(0); self.clear_errorsrc(); if inten { - r.intenset.write(|w| w.stopped().set().error().set()); + r.intenset().write(|w| { + w.set_stopped(true); + w.set_error(true); + }); } else { - r.intenclr.write(|w| w.stopped().clear().error().clear()); + r.intenclr().write(|w| { + w.set_stopped(true); + w.set_error(true); + }); } // Start write operation. - r.tasks_preparetx.write(|w| unsafe { w.bits(1) }); - r.tasks_resume.write(|w| unsafe { w.bits(1) }); + r.tasks_preparetx().write_value(1); + r.tasks_resume().write_value(1); Ok(()) } @@ -591,22 +591,30 @@ impl<'d, T: Instance> Twis<'d, T> { unsafe { self.set_rx_buffer(buffer)? }; // Clear events - r.events_read.reset(); - r.events_write.reset(); - r.events_stopped.reset(); - r.events_error.reset(); + r.events_read().write_value(0); + r.events_write().write_value(0); + r.events_stopped().write_value(0); + r.events_error().write_value(0); self.clear_errorsrc(); if inten { - r.intenset - .write(|w| w.stopped().set().error().set().read().set().write().set()); + r.intenset().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + w.set_write(true); + }); } else { - r.intenclr - .write(|w| w.stopped().clear().error().clear().read().clear().write().clear()); + r.intenclr().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + w.set_write(true); + }); } // Start read operation. - r.tasks_preparerx.write(|w| unsafe { w.bits(1) }); + r.tasks_preparerx().write_value(1); Ok(()) } @@ -616,16 +624,24 @@ impl<'d, T: Instance> Twis<'d, T> { compiler_fence(SeqCst); // Clear events - r.events_read.reset(); - r.events_write.reset(); - r.events_stopped.reset(); - r.events_error.reset(); + r.events_read().write_value(0); + r.events_write().write_value(0); + r.events_stopped().write_value(0); + r.events_error().write_value(0); self.clear_errorsrc(); if inten { - r.intenset.write(|w| w.stopped().set().error().set().read().set()); + r.intenset().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + }); } else { - r.intenclr.write(|w| w.stopped().clear().error().clear().read().clear()); + r.intenclr().write(|w| { + w.set_stopped(true); + w.set_error(true); + w.set_read(true); + }); } Ok(()) @@ -745,10 +761,10 @@ impl<'a, T: Instance> Drop for Twis<'a, T> { // disable! let r = T::regs(); - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); - gpio::deconfigure_pin(r.psel.sda.read().bits()); - gpio::deconfigure_pin(r.psel.scl.read().bits()); + gpio::deconfigure_pin(r.psel().sda().read()); + gpio::deconfigure_pin(r.psel().scl().read()); trace!("twis drop: done"); } @@ -767,13 +783,13 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::twis0::RegisterBlock; + fn regs() -> pac::twis::Twis; fn state() -> &'static State; } /// TWIS peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static { +pub trait Instance: SealedInstance + PeripheralType + 'static { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -781,8 +797,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static { macro_rules! impl_twis { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::twis::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::twis0::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::twis::Twis { + pac::$pac_type } fn state() -> &'static crate::twis::State { static STATE: crate::twis::State = crate::twis::State::new(); diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 4cf193617..f377df49e 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -19,19 +19,20 @@ use core::sync::atomic::{compiler_fence, AtomicU8, Ordering}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; -use pac::uarte0::RegisterBlock; // Re-export SVD variants to allow user to directly set values. -pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}; +pub use pac::uarte::vals::{Baudrate, ConfigParity as Parity}; use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; -use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits, SealedPin as _}; +use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits, SealedPin as _, DISCONNECTED}; use crate::interrupt::typelevel::Interrupt; +use crate::pac::gpio::vals as gpiovals; +use crate::pac::uarte::vals; use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::util::slice_in_ram_or; -use crate::{interrupt, pac, Peripheral}; +use crate::{interrupt, pac}; /// UARTE config. #[derive(Clone)] @@ -54,7 +55,7 @@ impl Default for Config { bitflags::bitflags! { /// Error source flags - pub struct ErrorSource: u32 { + pub(crate) struct ErrorSource: u32 { /// Buffer overrun const OVERRUN = 0x01; /// Parity error @@ -112,20 +113,20 @@ impl interrupt::typelevel::Handler for InterruptHandl let r = T::regs(); let s = T::state(); - let endrx = r.events_endrx.read().bits(); - let error = r.events_error.read().bits(); + let endrx = r.events_endrx().read(); + let error = r.events_error().read(); if endrx != 0 || error != 0 { s.rx_waker.wake(); if endrx != 0 { - r.intenclr.write(|w| w.endrx().clear()); + r.intenclr().write(|w| w.set_endrx(true)); } if error != 0 { - r.intenclr.write(|w| w.error().clear()); + r.intenclr().write(|w| w.set_error(true)); } } - if r.events_endtx.read().bits() != 0 { + if r.events_endtx().read() != 0 { s.tx_waker.wake(); - r.intenclr.write(|w| w.endtx().clear()); + r.intenclr().write(|w| w.set_endtx(true)); } } } @@ -140,56 +141,54 @@ pub struct Uarte<'d, T: Instance> { /// /// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteTx<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } /// Receiver part of the UARTE driver. /// /// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteRx<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, } impl<'d, T: Instance> Uarte<'d, T> { /// Create a new UARTE without hardware flow control pub fn new( - uarte: impl Peripheral

+ 'd, + uarte: Peri<'d, T>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

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

+ 'd, + uarte: Peri<'d, T>, + rxd: Peri<'d, impl GpioPin>, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

+ 'd, - cts: impl Peripheral

+ 'd, - rts: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(uarte, rxd, txd, cts, rts); Self::new_inner( uarte, - rxd.map_into(), - txd.map_into(), - Some(cts.map_into()), - Some(rts.map_into()), + rxd.into(), + txd.into(), + Some(cts.into()), + Some(rts.into()), config, ) } fn new_inner( - uarte: PeripheralRef<'d, T>, - rxd: PeripheralRef<'d, AnyPin>, - txd: PeripheralRef<'d, AnyPin>, - cts: Option>, - rts: Option>, + uarte: Peri<'d, T>, + rxd: Peri<'d, AnyPin>, + txd: Peri<'d, AnyPin>, + cts: Option>, + rts: Option>, config: Config, ) -> Self { let r = T::regs(); @@ -200,28 +199,12 @@ impl<'d, T: Instance> Uarte<'d, T> { _ => 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()) }); - - txd.set_high(); - txd.conf().write(|w| w.dir().output().drive().h0h1()); - r.psel.txd.write(|w| unsafe { w.bits(txd.psel_bits()) }); - - if let Some(pin) = &cts { - pin.conf().write(|w| w.input().connect().drive().h0h1()); - } - r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) }); - - if let Some(pin) = &rts { - pin.set_high(); - pin.conf().write(|w| w.dir().output().drive().h0h1()); - } - r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); + configure_rx_pins(r, rxd, rts); + configure_tx_pins(r, txd, cts); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); let s = T::state(); s.tx_rx_refcount.store(2, Ordering::Relaxed); @@ -254,9 +237,9 @@ impl<'d, T: Instance> Uarte<'d, T> { /// This is useful to concurrently transmit and receive from independent tasks. pub fn split_with_idle( self, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, + timer: Peri<'d, U>, + ppi_ch1: Peri<'d, impl ConfigurableChannel + 'd>, + ppi_ch2: Peri<'d, impl ConfigurableChannel + 'd>, ) -> (UarteTx<'d, T>, UarteRxWithIdle<'d, T, U>) { (self.tx, self.rx.with_idle(timer, ppi_ch1, ppi_ch2)) } @@ -264,7 +247,7 @@ impl<'d, T: Instance> Uarte<'d, T> { /// Return the endtx event for use with PPI pub fn event_endtx(&self) -> Event { let r = T::regs(); - Event::from_reg(&r.events_endtx) + Event::from_reg(r.events_endtx()) } /// Read bytes until the buffer is filled. @@ -298,27 +281,64 @@ impl<'d, T: Instance> Uarte<'d, T> { } } -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); - w +pub(crate) fn configure_tx_pins(r: pac::uarte::Uarte, txd: Peri<'_, AnyPin>, cts: Option>) { + txd.set_high(); + txd.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + w.set_drive(gpiovals::Drive::H0H1); }); - r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); + r.psel().txd().write_value(txd.psel_bits()); + + if let Some(pin) = &cts { + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(gpiovals::Drive::H0H1); + }); + } + r.psel().cts().write_value(cts.psel_bits()); +} + +pub(crate) fn configure_rx_pins(r: pac::uarte::Uarte, rxd: Peri<'_, AnyPin>, rts: Option>) { + rxd.conf().write(|w| { + w.set_dir(gpiovals::Dir::INPUT); + w.set_input(gpiovals::Input::CONNECT); + w.set_drive(gpiovals::Drive::H0H1); + }); + r.psel().rxd().write_value(rxd.psel_bits()); + + if let Some(pin) = &rts { + pin.set_high(); + pin.conf().write(|w| { + w.set_dir(gpiovals::Dir::OUTPUT); + w.set_input(gpiovals::Input::DISCONNECT); + w.set_drive(gpiovals::Drive::H0H1); + }); + } + r.psel().rts().write_value(rts.psel_bits()); +} + +pub(crate) fn configure(r: pac::uarte::Uarte, config: Config, hardware_flow_control: bool) { + r.config().write(|w| { + w.set_hwfc(hardware_flow_control); + w.set_parity(config.parity); + }); + r.baudrate().write(|w| w.set_baudrate(config.baudrate)); // Disable all interrupts - r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + r.intenclr().write(|w| w.0 = 0xFFFF_FFFF); // Reset rxstarted, txstarted. These are used by drop to know whether a transfer was // stopped midway or not. - r.events_rxstarted.reset(); - r.events_txstarted.reset(); + r.events_rxstarted().write_value(0); + r.events_txstarted().write_value(0); // 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()); + r.psel().txd().write_value(DISCONNECTED); + r.psel().rxd().write_value(DISCONNECTED); + r.psel().cts().write_value(DISCONNECTED); + r.psel().rts().write_value(DISCONNECTED); apply_workaround_for_enable_anomaly(r); } @@ -326,49 +346,34 @@ pub(crate) fn configure(r: &RegisterBlock, config: Config, hardware_flow_control impl<'d, T: Instance> UarteTx<'d, T> { /// Create a new tx-only UARTE without hardware flow control pub fn new( - uarte: impl Peripheral

+ 'd, + uarte: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - txd: impl Peripheral

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

+ 'd, + uarte: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - txd: impl Peripheral

+ 'd, - cts: impl Peripheral

+ 'd, + txd: Peri<'d, impl GpioPin>, + cts: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(uarte, txd, cts); - Self::new_inner(uarte, txd.map_into(), Some(cts.map_into()), config) + Self::new_inner(uarte, txd.into(), Some(cts.into()), config) } - fn new_inner( - uarte: PeripheralRef<'d, T>, - txd: PeripheralRef<'d, AnyPin>, - cts: Option>, - config: Config, - ) -> Self { + fn new_inner(uarte: Peri<'d, T>, txd: Peri<'d, AnyPin>, cts: Option>, config: Config) -> Self { 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()) }); - - if let Some(pin) = &cts { - pin.conf().write(|w| w.input().connect().drive().h0h1()); - } - r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) }); + configure_tx_pins(r, txd, cts); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); let s = T::state(); s.tx_rx_refcount.store(1, Ordering::Relaxed); @@ -410,29 +415,29 @@ impl<'d, T: Instance> UarteTx<'d, T> { let drop = OnDrop::new(move || { trace!("write drop: stopping"); - r.intenclr.write(|w| w.endtx().clear()); - r.events_txstopped.reset(); - r.tasks_stoptx.write(|w| unsafe { w.bits(1) }); + r.intenclr().write(|w| w.set_endtx(true)); + r.events_txstopped().write_value(0); + r.tasks_stoptx().write_value(1); // TX is stopped almost instantly, spinning is fine. - while r.events_endtx.read().bits() == 0 {} + while r.events_endtx().read() == 0 {} trace!("write drop: stopped"); }); - r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + r.txd().ptr().write_value(ptr as u32); + r.txd().maxcnt().write(|w| w.set_maxcnt(len as _)); - r.events_endtx.reset(); - r.intenset.write(|w| w.endtx().set()); + r.events_endtx().write_value(0); + r.intenset().write(|w| w.set_endtx(true)); compiler_fence(Ordering::SeqCst); trace!("starttx"); - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + r.tasks_starttx().write_value(1); poll_fn(|cx| { s.tx_waker.register(cx.waker()); - if r.events_endtx.read().bits() != 0 { + if r.events_endtx().read() != 0 { return Poll::Ready(()); } Poll::Pending @@ -440,7 +445,7 @@ impl<'d, T: Instance> UarteTx<'d, T> { .await; compiler_fence(Ordering::SeqCst); - r.events_txstarted.reset(); + r.events_txstarted().write_value(0); drop.defuse(); Ok(()) @@ -476,21 +481,21 @@ impl<'d, T: Instance> UarteTx<'d, T> { let r = T::regs(); - r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + r.txd().ptr().write_value(ptr as u32); + r.txd().maxcnt().write(|w| w.set_maxcnt(len as _)); - r.events_endtx.reset(); - r.intenclr.write(|w| w.endtx().clear()); + r.events_endtx().write_value(0); + r.intenclr().write(|w| w.set_endtx(true)); compiler_fence(Ordering::SeqCst); trace!("starttx"); - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + r.tasks_starttx().write_value(1); - while r.events_endtx.read().bits() == 0 {} + while r.events_endtx().read() == 0 {} compiler_fence(Ordering::SeqCst); - r.events_txstarted.reset(); + r.events_txstarted().write_value(0); Ok(()) } @@ -502,11 +507,11 @@ impl<'a, T: Instance> Drop for UarteTx<'a, T> { let r = T::regs(); - let did_stoptx = r.events_txstarted.read().bits() != 0; + let did_stoptx = r.events_txstarted().read() != 0; trace!("did_stoptx {}", did_stoptx); // Wait for txstopped, if needed. - while did_stoptx && r.events_txstopped.read().bits() == 0 {} + while did_stoptx && r.events_txstopped().read() == 0 {} let s = T::state(); @@ -517,57 +522,42 @@ impl<'a, T: Instance> Drop for UarteTx<'a, T> { impl<'d, T: Instance> UarteRx<'d, T> { /// Create a new rx-only UARTE without hardware flow control pub fn new( - uarte: impl Peripheral

+ 'd, + uarte: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

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

+ 'd, + uarte: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - rxd: impl Peripheral

+ 'd, - rts: impl Peripheral

+ 'd, + rxd: Peri<'d, impl GpioPin>, + rts: Peri<'d, impl GpioPin>, config: Config, ) -> Self { - into_ref!(uarte, rxd, rts); - Self::new_inner(uarte, rxd.map_into(), Some(rts.map_into()), config) + Self::new_inner(uarte, rxd.into(), Some(rts.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() + let err_bits = r.errorsrc().read(); + r.errorsrc().write_value(err_bits); + ErrorSource::from_bits_truncate(err_bits.0).check() } - fn new_inner( - uarte: PeripheralRef<'d, T>, - rxd: PeripheralRef<'d, AnyPin>, - rts: Option>, - config: Config, - ) -> Self { + fn new_inner(uarte: Peri<'d, T>, rxd: Peri<'d, AnyPin>, rts: Option>, config: Config) -> Self { 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()) }); - - if let Some(pin) = &rts { - pin.set_high(); - pin.conf().write(|w| w.dir().output().drive().h0h1()); - } - r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); + configure_rx_pins(r, rxd, rts); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - r.enable.write(|w| w.enable().enabled()); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); let s = T::state(); s.tx_rx_refcount.store(1, Ordering::Relaxed); @@ -578,14 +568,12 @@ impl<'d, T: Instance> UarteRx<'d, T> { /// Upgrade to an instance that supports idle line detection. pub fn with_idle( self, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, + timer: Peri<'d, U>, + ppi_ch1: Peri<'d, impl ConfigurableChannel + 'd>, + ppi_ch2: Peri<'d, impl ConfigurableChannel + 'd>, ) -> UarteRxWithIdle<'d, T, U> { let timer = Timer::new(timer); - into_ref!(ppi_ch1, ppi_ch2); - let r = T::regs(); // BAUDRATE register values are `baudrate * 2^32 / 16000000` @@ -594,8 +582,8 @@ impl<'d, T: Instance> UarteRx<'d, T> { // We want to stop RX if line is idle for 2 bytes worth of time // That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) // This gives us the amount of 16M ticks for 20 bits. - let baudrate = r.baudrate.read().baudrate().variant().unwrap(); - let timeout = 0x8000_0000 / (baudrate as u32 / 40); + let baudrate = r.baudrate().read().baudrate(); + let timeout = 0x8000_0000 / (baudrate.to_bits() / 40); timer.set_frequency(Frequency::F16MHz); timer.cc(0).write(timeout); @@ -603,17 +591,17 @@ impl<'d, T: Instance> UarteRx<'d, T> { timer.cc(0).short_compare_stop(); let mut ppi_ch1 = Ppi::new_one_to_two( - ppi_ch1.map_into(), - Event::from_reg(&r.events_rxdrdy), + ppi_ch1.into(), + Event::from_reg(r.events_rxdrdy()), timer.task_clear(), timer.task_start(), ); ppi_ch1.enable(); let mut ppi_ch2 = Ppi::new_one_to_one( - ppi_ch2.map_into(), + ppi_ch2.into(), timer.cc(0).event_compare(), - Task::from_reg(&r.tasks_stoprx), + Task::from_reg(r.tasks_stoprx()), ); ppi_ch2.enable(); @@ -643,43 +631,42 @@ impl<'d, T: Instance> UarteRx<'d, T> { let drop = OnDrop::new(move || { trace!("read drop: stopping"); - r.intenclr.write(|w| { - w.endrx().clear(); - w.error().clear() + r.intenclr().write(|w| { + w.set_endrx(true); + w.set_error(true); }); - r.events_rxto.reset(); - r.events_error.reset(); - r.errorsrc.reset(); - r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + r.events_rxto().write_value(0); + r.events_error().write_value(0); + r.tasks_stoprx().write_value(1); - while r.events_endrx.read().bits() == 0 {} + while r.events_endrx().read() == 0 {} trace!("read drop: stopped"); }); - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); - r.events_endrx.reset(); - r.events_error.reset(); - r.intenset.write(|w| { - w.endrx().set(); - w.error().set() + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenset().write(|w| { + w.set_endrx(true); + w.set_error(true); }); compiler_fence(Ordering::SeqCst); trace!("startrx"); - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + r.tasks_startrx().write_value(1); 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) }); + r.tasks_stoprx().write_value(1); return Poll::Ready(Err(e)); } - if r.events_endrx.read().bits() != 0 { + if r.events_endrx().read() != 0 { return Poll::Ready(Ok(())); } Poll::Pending @@ -687,7 +674,7 @@ impl<'d, T: Instance> UarteRx<'d, T> { .await; compiler_fence(Ordering::SeqCst); - r.events_rxstarted.reset(); + r.events_rxstarted().write_value(0); drop.defuse(); result @@ -707,25 +694,25 @@ impl<'d, T: Instance> UarteRx<'d, T> { let r = T::regs(); - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); - r.events_endrx.reset(); - r.events_error.reset(); - r.intenclr.write(|w| { - w.endrx().clear(); - w.error().clear() + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenclr().write(|w| { + w.set_endrx(true); + w.set_error(true); }); compiler_fence(Ordering::SeqCst); trace!("startrx"); - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + r.tasks_startrx().write_value(1); - while r.events_endrx.read().bits() == 0 && r.events_error.read().bits() == 0 {} + while r.events_endrx().read() == 0 && r.events_error().read() == 0 {} compiler_fence(Ordering::SeqCst); - r.events_rxstarted.reset(); + r.events_rxstarted().write_value(0); self.check_and_clear_errors() } @@ -737,11 +724,11 @@ impl<'a, T: Instance> Drop for UarteRx<'a, T> { let r = T::regs(); - let did_stoprx = r.events_rxstarted.read().bits() != 0; + let did_stoprx = r.events_rxstarted().read() != 0; trace!("did_stoprx {}", did_stoprx); // Wait for rxto, if needed. - while did_stoprx && r.events_rxto.read().bits() == 0 {} + while did_stoprx && r.events_rxto().read() == 0 {} let s = T::state(); @@ -794,39 +781,39 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { let drop = OnDrop::new(|| { self.timer.stop(); - r.intenclr.write(|w| { - w.endrx().clear(); - w.error().clear() + r.intenclr().write(|w| { + w.set_endrx(true); + w.set_error(true); }); - r.events_rxto.reset(); - r.events_error.reset(); - r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + r.events_rxto().write_value(0); + r.events_error().write_value(0); + r.tasks_stoprx().write_value(1); - while r.events_endrx.read().bits() == 0 {} + while r.events_endrx().read() == 0 {} }); - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); - r.events_endrx.reset(); - r.events_error.reset(); - r.intenset.write(|w| { - w.endrx().set(); - w.error().set() + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenset().write(|w| { + w.set_endrx(true); + w.set_error(true); }); compiler_fence(Ordering::SeqCst); - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + r.tasks_startrx().write_value(1); 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) }); + r.tasks_stoprx().write_value(1); return Poll::Ready(Err(e)); } - if r.events_endrx.read().bits() != 0 { + if r.events_endrx().read() != 0 { return Poll::Ready(Ok(())); } @@ -835,10 +822,10 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { .await; compiler_fence(Ordering::SeqCst); - let n = r.rxd.amount.read().amount().bits() as usize; + let n = r.rxd().amount().read().0 as usize; self.timer.stop(); - r.events_rxstarted.reset(); + r.events_rxstarted().write_value(0); drop.defuse(); @@ -863,56 +850,57 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { self.ppi_ch1.enable(); - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + r.rxd().ptr().write_value(ptr as u32); + r.rxd().maxcnt().write(|w| w.set_maxcnt(len as _)); - r.events_endrx.reset(); - r.events_error.reset(); - r.intenclr.write(|w| { - w.endrx().clear(); - w.error().clear() + r.events_endrx().write_value(0); + r.events_error().write_value(0); + r.intenclr().write(|w| { + w.set_endrx(true); + w.set_error(true); }); compiler_fence(Ordering::SeqCst); - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + r.tasks_startrx().write_value(1); - while r.events_endrx.read().bits() == 0 && r.events_error.read().bits() == 0 {} + while r.events_endrx().read() == 0 && r.events_error().read() == 0 {} compiler_fence(Ordering::SeqCst); - let n = r.rxd.amount.read().amount().bits() as usize; + let n = r.rxd().amount().read().0 as usize; self.timer.stop(); - r.events_rxstarted.reset(); + r.events_rxstarted().write_value(0); self.rx.check_and_clear_errors().map(|_| n) } } #[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))] -pub(crate) fn apply_workaround_for_enable_anomaly(_r: &crate::pac::uarte0::RegisterBlock) { +pub(crate) fn apply_workaround_for_enable_anomaly(_r: pac::uarte::Uarte) { // Do nothing } #[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))] -pub(crate) fn apply_workaround_for_enable_anomaly(r: &crate::pac::uarte0::RegisterBlock) { +pub(crate) fn apply_workaround_for_enable_anomaly(r: pac::uarte::Uarte) { // Apply workaround for anomalies: // - nRF9160 - anomaly 23 // - nRF5340 - anomaly 44 - let rxenable_reg: *const u32 = ((r as *const _ as usize) + 0x564) as *const u32; - let txenable_reg: *const u32 = ((r as *const _ as usize) + 0x568) as *const u32; + let rp = r.as_ptr() as *mut u32; + let rxenable_reg = unsafe { rp.add(0x564 / 4) }; + let txenable_reg = unsafe { rp.add(0x568 / 4) }; // NB Safety: This is taken from Nordic's driver - // https://github.com/NordicSemiconductor/nrfx/blob/master/drivers/src/nrfx_uarte.c#L197 if unsafe { core::ptr::read_volatile(txenable_reg) } == 1 { - r.tasks_stoptx.write(|w| unsafe { w.bits(1) }); + r.tasks_stoptx().write_value(1); } // NB Safety: This is taken from Nordic's driver - // https://github.com/NordicSemiconductor/nrfx/blob/master/drivers/src/nrfx_uarte.c#L197 if unsafe { core::ptr::read_volatile(rxenable_reg) } == 1 { - r.enable.write(|w| w.enable().enabled()); - r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + r.enable().write(|w| w.set_enable(vals::Enable::ENABLED)); + r.tasks_stoprx().write_value(1); let mut workaround_succeded = false; // The UARTE is able to receive up to four bytes after the STOPRX task has been triggered. @@ -933,23 +921,23 @@ pub(crate) fn apply_workaround_for_enable_anomaly(r: &crate::pac::uarte0::Regist panic!("Failed to apply workaround for UART"); } - let errors = r.errorsrc.read().bits(); - // NB Safety: safe to write back the bits we just read to clear them - r.errorsrc.write(|w| unsafe { w.bits(errors) }); - r.enable.write(|w| w.enable().disabled()); + // write back the bits we just read to clear them + let errors = r.errorsrc().read(); + r.errorsrc().write_value(errors); + r.enable().write(|w| w.set_enable(vals::Enable::DISABLED)); } } -pub(crate) fn drop_tx_rx(r: &pac::uarte0::RegisterBlock, s: &State) { +pub(crate) fn drop_tx_rx(r: pac::uarte::Uarte, s: &State) { if s.tx_rx_refcount.fetch_sub(1, Ordering::Relaxed) == 1 { // Finally we can disable, and we do so for the peripheral // i.e. not just rx concerns. - r.enable.write(|w| w.enable().disabled()); + r.enable().write(|w| w.set_enable(vals::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()); + gpio::deconfigure_pin(r.psel().rxd().read()); + gpio::deconfigure_pin(r.psel().txd().read()); + gpio::deconfigure_pin(r.psel().rts().read()); + gpio::deconfigure_pin(r.psel().cts().read()); trace!("uarte tx and rx drop: done"); } @@ -971,14 +959,14 @@ impl State { } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::uarte0::RegisterBlock; + fn regs() -> pac::uarte::Uarte; fn state() -> &'static State; fn buffered_state() -> &'static crate::buffered_uarte::State; } /// UARTE peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -986,8 +974,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_uarte { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::uarte::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::uarte0::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::uarte::Uarte { + pac::$pac_type } fn state() -> &'static crate::uarte::State { static STATE: crate::uarte::State = crate::uarte::State::new(); @@ -1033,3 +1021,42 @@ mod eh02 { } } } + +mod _embedded_io { + use super::*; + + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + match *self { + Error::BufferTooLong => embedded_io_async::ErrorKind::InvalidInput, + Error::BufferNotInRAM => embedded_io_async::ErrorKind::Unsupported, + Error::Framing => embedded_io_async::ErrorKind::InvalidData, + Error::Parity => embedded_io_async::ErrorKind::InvalidData, + Error::Overrun => embedded_io_async::ErrorKind::OutOfMemory, + Error::Break => embedded_io_async::ErrorKind::ConnectionAborted, + } + } + } + + impl<'d, U: Instance> embedded_io_async::ErrorType for Uarte<'d, U> { + type Error = Error; + } + + impl<'d, U: Instance> embedded_io_async::ErrorType for UarteTx<'d, U> { + type Error = Error; + } + + impl<'d, U: Instance> embedded_io_async::Write for Uarte<'d, U> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + } + + impl<'d: 'd, U: Instance> embedded_io_async::Write for UarteTx<'d, U> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) + } + } +} diff --git a/embassy-nrf/src/usb/mod.rs b/embassy-nrf/src/usb/mod.rs index 8cbb1a350..6cc1b0111 100644 --- a/embassy-nrf/src/usb/mod.rs +++ b/embassy-nrf/src/usb/mod.rs @@ -4,29 +4,28 @@ pub mod vbus_detect; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::mem::MaybeUninit; use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use core::task::Poll; use cortex_m::peripheral::NVIC; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use embassy_usb_driver as driver; use embassy_usb_driver::{Direction, EndpointAddress, EndpointError, EndpointInfo, EndpointType, Event, Unsupported}; -use pac::usbd::RegisterBlock; use self::vbus_detect::VbusDetect; use crate::interrupt::typelevel::Interrupt; +use crate::pac::usbd::vals; use crate::util::slice_in_ram; -use crate::{interrupt, pac, Peripheral}; +use crate::{interrupt, pac}; -const NEW_AW: AtomicWaker = AtomicWaker::new(); -static BUS_WAKER: AtomicWaker = NEW_AW; -static EP0_WAKER: AtomicWaker = NEW_AW; -static EP_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; -static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; +static BUS_WAKER: AtomicWaker = AtomicWaker::new(); +static EP0_WAKER: AtomicWaker = AtomicWaker::new(); +static EP_IN_WAKERS: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8]; +static EP_OUT_WAKERS: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8]; static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); /// Interrupt handler. @@ -38,19 +37,19 @@ impl interrupt::typelevel::Handler for InterruptHandl unsafe fn on_interrupt() { let regs = T::regs(); - if regs.events_usbreset.read().bits() != 0 { - regs.intenclr.write(|w| w.usbreset().clear()); + if regs.events_usbreset().read() != 0 { + regs.intenclr().write(|w| w.set_usbreset(true)); BUS_WAKER.wake(); EP0_WAKER.wake(); } - if regs.events_ep0setup.read().bits() != 0 { - regs.intenclr.write(|w| w.ep0setup().clear()); + if regs.events_ep0setup().read() != 0 { + regs.intenclr().write(|w| w.set_ep0setup(true)); EP0_WAKER.wake(); } - if regs.events_ep0datadone.read().bits() != 0 { - regs.intenclr.write(|w| w.ep0datadone().clear()); + if regs.events_ep0datadone().read() != 0 { + regs.intenclr().write(|w| w.set_ep0datadone(true)); EP0_WAKER.wake(); } @@ -63,22 +62,22 @@ impl interrupt::typelevel::Handler for InterruptHandl // Therefore, it's fine to clear just the event, and let main thread // check the individual bits in EVENTCAUSE and EPDATASTATUS. It // doesn't cause an infinite irq loop. - if regs.events_usbevent.read().bits() != 0 { - regs.events_usbevent.reset(); + if regs.events_usbevent().read() != 0 { + regs.events_usbevent().write_value(0); BUS_WAKER.wake(); } - if regs.events_epdata.read().bits() != 0 { - regs.events_epdata.reset(); + if regs.events_epdata().read() != 0 { + regs.events_epdata().write_value(0); - let r = regs.epdatastatus.read().bits(); - regs.epdatastatus.write(|w| unsafe { w.bits(r) }); - READY_ENDPOINTS.fetch_or(r, Ordering::AcqRel); + let r = regs.epdatastatus().read(); + regs.epdatastatus().write_value(r); + READY_ENDPOINTS.fetch_or(r.0, Ordering::AcqRel); for i in 1..=7 { - if r & In::mask(i) != 0 { + if r.0 & In::mask(i) != 0 { In::waker(i).wake(); } - if r & Out::mask(i) != 0 { + if r.0 & Out::mask(i) != 0 { Out::waker(i).wake(); } } @@ -88,7 +87,7 @@ impl interrupt::typelevel::Handler for InterruptHandl /// USB driver. pub struct Driver<'d, T: Instance, V: VbusDetect> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, alloc_in: Allocator, alloc_out: Allocator, vbus_detect: V, @@ -97,12 +96,10 @@ pub struct Driver<'d, T: Instance, V: VbusDetect> { impl<'d, T: Instance, V: VbusDetect> Driver<'d, T, V> { /// Create a new USB driver. pub fn new( - usb: impl Peripheral

+ 'd, + usb: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, vbus_detect: V, ) -> Self { - into_ref!(usb); - T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; @@ -170,7 +167,7 @@ impl<'d, T: Instance, V: VbusDetect + 'd> driver::Driver<'d> for Driver<'d, T, V /// USB bus. pub struct Bus<'d, T: Instance, V: VbusDetect> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, power_available: bool, vbus_detect: V, } @@ -181,35 +178,34 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { errata::pre_enable(); - regs.enable.write(|w| w.enable().enabled()); + regs.enable().write(|w| w.set_enable(true)); // Wait until the peripheral is ready. - regs.intenset.write(|w| w.usbevent().set_bit()); + regs.intenset().write(|w| w.set_usbevent(true)); poll_fn(|cx| { BUS_WAKER.register(cx.waker()); - if regs.eventcause.read().ready().is_ready() { + if regs.eventcause().read().ready() { Poll::Ready(()) } else { Poll::Pending } }) .await; - regs.eventcause.write(|w| w.ready().clear_bit_by_one()); + regs.eventcause().write(|w| w.set_ready(true)); errata::post_enable(); unsafe { NVIC::unmask(pac::Interrupt::USBD) }; - regs.intenset.write(|w| { - w.usbreset().set_bit(); - w.usbevent().set_bit(); - w.epdata().set_bit(); - w + regs.intenset().write(|w| { + w.set_usbreset(true); + w.set_usbevent(true); + w.set_epdata(true); }); if self.vbus_detect.wait_power_ready().await.is_ok() { // Enable the USB pullup, allowing enumeration. - regs.usbpullup.write(|w| w.connect().enabled()); + regs.usbpullup().write(|w| w.set_connect(true)); trace!("enabled"); } else { trace!("usb power not ready due to usb removal"); @@ -218,21 +214,21 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { async fn disable(&mut self) { let regs = T::regs(); - regs.enable.write(|x| x.enable().disabled()); + regs.enable().write(|x| x.set_enable(false)); } - async fn poll(&mut self) -> Event { - poll_fn(move |cx| { + fn poll(&mut self) -> impl Future { + poll_fn(|cx| { BUS_WAKER.register(cx.waker()); let regs = T::regs(); - if regs.events_usbreset.read().bits() != 0 { - regs.events_usbreset.reset(); - regs.intenset.write(|w| w.usbreset().set()); + if regs.events_usbreset().read() != 0 { + regs.events_usbreset().write_value(0); + regs.intenset().write(|w| w.set_usbreset(true)); // Disable all endpoints except EP0 - regs.epinen.write(|w| unsafe { w.bits(0x01) }); - regs.epouten.write(|w| unsafe { w.bits(0x01) }); + regs.epinen().write(|w| w.0 = 0x01); + regs.epouten().write(|w| w.0 = 0x01); READY_ENDPOINTS.store(In::mask(0), Ordering::Release); for i in 1..=7 { In::waker(i).wake(); @@ -242,27 +238,27 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { return Poll::Ready(Event::Reset); } - let r = regs.eventcause.read(); + let r = regs.eventcause().read(); - if r.isooutcrc().bit() { - regs.eventcause.write(|w| w.isooutcrc().detected()); + if r.isooutcrc() { + regs.eventcause().write(|w| w.set_isooutcrc(true)); trace!("USB event: isooutcrc"); } - if r.usbwuallowed().bit() { - regs.eventcause.write(|w| w.usbwuallowed().allowed()); + if r.usbwuallowed() { + regs.eventcause().write(|w| w.set_usbwuallowed(true)); trace!("USB event: usbwuallowed"); } - if r.suspend().bit() { - regs.eventcause.write(|w| w.suspend().detected()); - regs.lowpower.write(|w| w.lowpower().low_power()); + if r.suspend() { + regs.eventcause().write(|w| w.set_suspend(true)); + regs.lowpower().write(|w| w.set_lowpower(vals::Lowpower::LOW_POWER)); return Poll::Ready(Event::Suspend); } - if r.resume().bit() { - regs.eventcause.write(|w| w.resume().detected()); + if r.resume() { + regs.eventcause().write(|w| w.set_resume(true)); return Poll::Ready(Event::Resume); } - if r.ready().bit() { - regs.eventcause.write(|w| w.ready().ready()); + if r.ready() { + regs.eventcause().write(|w| w.set_ready(true)); trace!("USB event: ready"); } @@ -279,21 +275,23 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { Poll::Pending }) - .await } fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { let regs = T::regs(); - unsafe { - if ep_addr.index() == 0 { - regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(stalled)); - } else { - regs.epstall.write(|w| { - w.ep().bits(ep_addr.index() as u8 & 0b111); - w.io().bit(ep_addr.is_in()); - w.stall().bit(stalled) - }); + if ep_addr.index() == 0 { + if stalled { + regs.tasks_ep0stall().write_value(1); } + } else { + regs.epstall().write(|w| { + w.set_ep(ep_addr.index() as u8 & 0b111); + w.set_io(match ep_addr.direction() { + Direction::In => vals::Io::IN, + Direction::Out => vals::Io::OUT, + }); + w.set_stall(stalled); + }); } } @@ -301,8 +299,8 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { let regs = T::regs(); let i = ep_addr.index(); match ep_addr.direction() { - Direction::Out => regs.halted.epout[i].read().getstatus().is_halted(), - Direction::In => regs.halted.epin[i].read().getstatus().is_halted(), + Direction::Out => regs.halted().epout(i).read().getstatus() == vals::Getstatus::HALTED, + Direction::In => regs.halted().epin(i).read().getstatus() == vals::Getstatus::HALTED, } } @@ -317,15 +315,13 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { match ep_addr.direction() { Direction::In => { let mut was_enabled = false; - regs.epinen.modify(|r, w| { - let mut bits = r.bits(); - was_enabled = (bits & mask) != 0; + regs.epinen().modify(|w| { + was_enabled = (w.0 & mask) != 0; if enabled { - bits |= mask + w.0 |= mask } else { - bits &= !mask + w.0 &= !mask } - unsafe { w.bits(bits) } }); let ready_mask = In::mask(i); @@ -340,15 +336,8 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { In::waker(i).wake(); } Direction::Out => { - regs.epouten.modify(|r, w| { - let mut bits = r.bits(); - if enabled { - bits |= mask - } else { - bits &= !mask - } - unsafe { w.bits(bits) } - }); + regs.epouten() + .modify(|w| if enabled { w.0 |= mask } else { w.0 &= !mask }); let ready_mask = Out::mask(i); if enabled { @@ -356,7 +345,7 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { // peripheral will NAK all incoming packets) until we write a zero to the SIZE // register (see figure 203 of the 52840 manual). To avoid that we write a 0 to the // SIZE register - regs.size.epout[i].reset(); + regs.size().epout(i).write(|_| ()); } else { READY_ENDPOINTS.fetch_and(!ready_mask, Ordering::AcqRel); } @@ -370,25 +359,24 @@ impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { let regs = T::regs(); - if regs.lowpower.read().lowpower().is_low_power() { + if regs.lowpower().read().lowpower() == vals::Lowpower::LOW_POWER { errata::pre_wakeup(); - regs.lowpower.write(|w| w.lowpower().force_normal()); + regs.lowpower().write(|w| w.set_lowpower(vals::Lowpower::FORCE_NORMAL)); poll_fn(|cx| { BUS_WAKER.register(cx.waker()); let regs = T::regs(); - let r = regs.eventcause.read(); + let r = regs.eventcause().read(); - if regs.events_usbreset.read().bits() != 0 { + if regs.events_usbreset().read() != 0 { Poll::Ready(()) - } else if r.resume().bit() { + } else if r.resume() { Poll::Ready(()) - } else if r.usbwuallowed().bit() { - regs.eventcause.write(|w| w.usbwuallowed().allowed()); - - regs.dpdmvalue.write(|w| w.state().resume()); - regs.tasks_dpdmdrive.write(|w| w.tasks_dpdmdrive().set_bit()); + } else if r.usbwuallowed() { + regs.eventcause().write(|w| w.set_usbwuallowed(true)); + regs.dpdmvalue().write(|w| w.set_state(vals::State::RESUME)); + regs.tasks_dpdmdrive().write_value(1); Poll::Ready(()) } else { @@ -413,7 +401,7 @@ pub enum In {} trait EndpointDir { fn waker(i: usize) -> &'static AtomicWaker; fn mask(i: usize) -> u32; - fn is_enabled(regs: &RegisterBlock, i: usize) -> bool; + fn is_enabled(regs: pac::usbd::Usbd, i: usize) -> bool; } impl EndpointDir for In { @@ -428,8 +416,8 @@ impl EndpointDir for In { } #[inline] - fn is_enabled(regs: &RegisterBlock, i: usize) -> bool { - (regs.epinen.read().bits() & (1 << i)) != 0 + fn is_enabled(regs: pac::usbd::Usbd, i: usize) -> bool { + regs.epinen().read().in_(i) } } @@ -445,8 +433,8 @@ impl EndpointDir for Out { } #[inline] - fn is_enabled(regs: &RegisterBlock, i: usize) -> bool { - (regs.epouten.read().bits() & (1 << i)) != 0 + fn is_enabled(regs: pac::usbd::Usbd, i: usize) -> bool { + regs.epouten().read().out(i) } } @@ -477,7 +465,7 @@ impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir #[allow(private_bounds)] impl<'d, T: Instance, Dir: EndpointDir> Endpoint<'d, T, Dir> { - async fn wait_enabled_state(&mut self, state: bool) { + fn wait_enabled_state(&mut self, state: bool) -> impl Future { let i = self.info.addr.index(); assert!(i != 0); @@ -489,12 +477,11 @@ impl<'d, T: Instance, Dir: EndpointDir> Endpoint<'d, T, Dir> { Poll::Pending } }) - .await } /// Wait for the endpoint to be disabled - pub async fn wait_disabled(&mut self) { - self.wait_enabled_state(false).await + pub fn wait_disabled(&mut self) -> impl Future { + self.wait_enabled_state(false) } } @@ -529,33 +516,23 @@ unsafe fn read_dma(i: usize, buf: &mut [u8]) -> Result buf.len() { return Err(EndpointError::BufferOverflow); } - let epout = [ - ®s.epout0, - ®s.epout1, - ®s.epout2, - ®s.epout3, - ®s.epout4, - ®s.epout5, - ®s.epout6, - ®s.epout7, - ]; - epout[i].ptr.write(|w| w.bits(buf.as_ptr() as u32)); + regs.epout(i).ptr().write_value(buf.as_ptr() as u32); // MAXCNT must match SIZE - epout[i].maxcnt.write(|w| w.bits(size as u32)); + regs.epout(i).maxcnt().write(|w| w.set_maxcnt(size as _)); dma_start(); - regs.events_endepout[i].reset(); - regs.tasks_startepout[i].write(|w| w.tasks_startepout().set_bit()); - while regs.events_endepout[i].read().events_endepout().bit_is_clear() {} - regs.events_endepout[i].reset(); + regs.events_endepout(i).write_value(0); + regs.tasks_startepout(i).write_value(1); + while regs.events_endepout(i).read() == 0 {} + regs.events_endepout(i).write_value(0); dma_end(); - regs.size.epout[i].reset(); + regs.size().epout(i).write(|_| ()); Ok(size) } @@ -574,27 +551,16 @@ unsafe fn write_dma(i: usize, buf: &[u8]) { buf.as_ptr() }; - let epin = [ - ®s.epin0, - ®s.epin1, - ®s.epin2, - ®s.epin3, - ®s.epin4, - ®s.epin5, - ®s.epin6, - ®s.epin7, - ]; - // Set the buffer length so the right number of bytes are transmitted. // Safety: `buf.len()` has been checked to be <= the max buffer length. - epin[i].ptr.write(|w| w.bits(ptr as u32)); - epin[i].maxcnt.write(|w| w.maxcnt().bits(buf.len() as u8)); + regs.epin(i).ptr().write_value(ptr as u32); + regs.epin(i).maxcnt().write(|w| w.set_maxcnt(buf.len() as u8)); - regs.events_endepin[i].reset(); + regs.events_endepin(i).write_value(0); dma_start(); - regs.tasks_startepin[i].write(|w| w.bits(1)); - while regs.events_endepin[i].read().bits() == 0 {} + regs.tasks_startepin(i).write_value(1); + while regs.events_endepin(i).read() == 0 {} dma_end(); } @@ -624,7 +590,7 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { /// USB control pipe. pub struct ControlPipe<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + _p: Peri<'d, T>, max_packet_size: u16, } @@ -637,14 +603,14 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { let regs = T::regs(); // Reset shorts - regs.shorts.write(|w| w); + regs.shorts().write(|_| ()); // Wait for SETUP packet - regs.intenset.write(|w| w.ep0setup().set()); + regs.intenset().write(|w| w.set_ep0setup(true)); poll_fn(|cx| { EP0_WAKER.register(cx.waker()); let regs = T::regs(); - if regs.events_ep0setup.read().bits() != 0 { + if regs.events_ep0setup().read() != 0 { Poll::Ready(()) } else { Poll::Pending @@ -652,17 +618,17 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { }) .await; - regs.events_ep0setup.reset(); + regs.events_ep0setup().write_value(0); let mut buf = [0; 8]; - buf[0] = regs.bmrequesttype.read().bits() as u8; - buf[1] = regs.brequest.read().brequest().bits(); - buf[2] = regs.wvaluel.read().wvaluel().bits(); - buf[3] = regs.wvalueh.read().wvalueh().bits(); - buf[4] = regs.windexl.read().windexl().bits(); - buf[5] = regs.windexh.read().windexh().bits(); - buf[6] = regs.wlengthl.read().wlengthl().bits(); - buf[7] = regs.wlengthh.read().wlengthh().bits(); + buf[0] = regs.bmrequesttype().read().0 as u8; + buf[1] = regs.brequest().read().0 as u8; + buf[2] = regs.wvaluel().read().0 as u8; + buf[3] = regs.wvalueh().read().0 as u8; + buf[4] = regs.windexl().read().0 as u8; + buf[5] = regs.windexh().read().0 as u8; + buf[6] = regs.wlengthl().read().0 as u8; + buf[7] = regs.wlengthh().read().0 as u8; buf } @@ -670,26 +636,26 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { let regs = T::regs(); - regs.events_ep0datadone.reset(); + regs.events_ep0datadone().write_value(0); // This starts a RX on EP0. events_ep0datadone notifies when done. - regs.tasks_ep0rcvout.write(|w| w.tasks_ep0rcvout().set_bit()); + regs.tasks_ep0rcvout().write_value(1); // Wait until ready - regs.intenset.write(|w| { - w.usbreset().set(); - w.ep0setup().set(); - w.ep0datadone().set() + regs.intenset().write(|w| { + w.set_usbreset(true); + w.set_ep0setup(true); + w.set_ep0datadone(true); }); poll_fn(|cx| { EP0_WAKER.register(cx.waker()); let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { + if regs.events_ep0datadone().read() != 0 { Poll::Ready(Ok(())) - } else if regs.events_usbreset.read().bits() != 0 { + } else if regs.events_usbreset().read() != 0 { trace!("aborted control data_out: usb reset"); Poll::Ready(Err(EndpointError::Disabled)) - } else if regs.events_ep0setup.read().bits() != 0 { + } else if regs.events_ep0setup().read() != 0 { trace!("aborted control data_out: received another SETUP"); Poll::Ready(Err(EndpointError::Disabled)) } else { @@ -703,29 +669,29 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { async fn data_in(&mut self, buf: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { let regs = T::regs(); - regs.events_ep0datadone.reset(); + regs.events_ep0datadone().write_value(0); - regs.shorts.write(|w| w.ep0datadone_ep0status().bit(last)); + regs.shorts().write(|w| w.set_ep0datadone_ep0status(last)); // This starts a TX on EP0. events_ep0datadone notifies when done. unsafe { write_dma::(0, buf) } - regs.intenset.write(|w| { - w.usbreset().set(); - w.ep0setup().set(); - w.ep0datadone().set() + regs.intenset().write(|w| { + w.set_usbreset(true); + w.set_ep0setup(true); + w.set_ep0datadone(true); }); poll_fn(|cx| { cx.waker().wake_by_ref(); EP0_WAKER.register(cx.waker()); let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { + if regs.events_ep0datadone().read() != 0 { Poll::Ready(Ok(())) - } else if regs.events_usbreset.read().bits() != 0 { + } else if regs.events_usbreset().read() != 0 { trace!("aborted control data_in: usb reset"); Poll::Ready(Err(EndpointError::Disabled)) - } else if regs.events_ep0setup.read().bits() != 0 { + } else if regs.events_ep0setup().read() != 0 { trace!("aborted control data_in: received another SETUP"); Poll::Ready(Err(EndpointError::Disabled)) } else { @@ -737,12 +703,12 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { async fn accept(&mut self) { let regs = T::regs(); - regs.tasks_ep0status.write(|w| w.tasks_ep0status().bit(true)); + regs.tasks_ep0status().write_value(1); } async fn reject(&mut self) { let regs = T::regs(); - regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); + regs.tasks_ep0stall().write_value(1); } async fn accept_set_address(&mut self, _addr: u8) { @@ -806,12 +772,12 @@ impl Allocator { } pub(crate) trait SealedInstance { - fn regs() -> &'static pac::usbd::RegisterBlock; + fn regs() -> pac::usbd::Usbd; } /// USB peripheral instance. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -819,8 +785,8 @@ pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { macro_rules! impl_usb { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::usb::SealedInstance for peripherals::$type { - fn regs() -> &'static pac::usbd::RegisterBlock { - unsafe { &*pac::$pac_type::ptr() } + fn regs() -> pac::usbd::Usbd { + pac::$pac_type } } impl crate::usb::Instance for peripherals::$type { diff --git a/embassy-nrf/src/usb/vbus_detect.rs b/embassy-nrf/src/usb/vbus_detect.rs index a05e5aa52..8794beb2d 100644 --- a/embassy-nrf/src/usb/vbus_detect.rs +++ b/embassy-nrf/src/usb/vbus_detect.rs @@ -1,6 +1,6 @@ //! Trait and implementations for performing VBUS detection. -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; @@ -29,14 +29,14 @@ pub trait VbusDetect { } #[cfg(not(feature = "_nrf5340"))] -type UsbRegIrq = interrupt::typelevel::POWER_CLOCK; +type UsbRegIrq = interrupt::typelevel::CLOCK_POWER; #[cfg(feature = "_nrf5340")] type UsbRegIrq = interrupt::typelevel::USBREGULATOR; #[cfg(not(feature = "_nrf5340"))] -type UsbRegPeri = pac::POWER; +const USB_REG_PERI: pac::power::Power = pac::POWER; #[cfg(feature = "_nrf5340")] -type UsbRegPeri = pac::USBREGULATOR; +const USB_REG_PERI: pac::usbreg::Usbreg = pac::USBREGULATOR; /// Interrupt handler. pub struct InterruptHandler { @@ -45,21 +45,21 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let regs = unsafe { &*UsbRegPeri::ptr() }; + let regs = USB_REG_PERI; - if regs.events_usbdetected.read().bits() != 0 { - regs.events_usbdetected.reset(); + if regs.events_usbdetected().read() != 0 { + regs.events_usbdetected().write_value(0); BUS_WAKER.wake(); } - if regs.events_usbremoved.read().bits() != 0 { - regs.events_usbremoved.reset(); + if regs.events_usbremoved().read() != 0 { + regs.events_usbremoved().write_value(0); BUS_WAKER.wake(); POWER_WAKER.wake(); } - if regs.events_usbpwrrdy.read().bits() != 0 { - regs.events_usbpwrrdy.reset(); + if regs.events_usbpwrrdy().read() != 0 { + regs.events_usbpwrrdy().write_value(0); POWER_WAKER.wake(); } } @@ -78,13 +78,16 @@ static POWER_WAKER: AtomicWaker = AtomicWaker::new(); impl HardwareVbusDetect { /// Create a new `VbusDetectNative`. pub fn new(_irq: impl interrupt::typelevel::Binding + 'static) -> Self { - let regs = unsafe { &*UsbRegPeri::ptr() }; + let regs = USB_REG_PERI; UsbRegIrq::unpend(); unsafe { UsbRegIrq::enable() }; - regs.intenset - .write(|w| w.usbdetected().set().usbremoved().set().usbpwrrdy().set()); + regs.intenset().write(|w| { + w.set_usbdetected(true); + w.set_usbremoved(true); + w.set_usbpwrrdy(true); + }); Self { _private: () } } @@ -92,16 +95,16 @@ impl HardwareVbusDetect { impl VbusDetect for HardwareVbusDetect { fn is_usb_detected(&self) -> bool { - let regs = unsafe { &*UsbRegPeri::ptr() }; - regs.usbregstatus.read().vbusdetect().is_vbus_present() + let regs = USB_REG_PERI; + regs.usbregstatus().read().vbusdetect() } - async fn wait_power_ready(&mut self) -> Result<(), ()> { - poll_fn(move |cx| { + fn wait_power_ready(&mut self) -> impl Future> { + poll_fn(|cx| { POWER_WAKER.register(cx.waker()); - let regs = unsafe { &*UsbRegPeri::ptr() }; + let regs = USB_REG_PERI; - if regs.usbregstatus.read().outputrdy().is_ready() { + if regs.usbregstatus().read().outputrdy() { Poll::Ready(Ok(())) } else if !self.is_usb_detected() { Poll::Ready(Err(())) @@ -109,7 +112,6 @@ impl VbusDetect for HardwareVbusDetect { Poll::Pending } }) - .await } } @@ -160,7 +162,7 @@ impl VbusDetect for &SoftwareVbusDetect { self.usb_detected.load(Ordering::Relaxed) } - async fn wait_power_ready(&mut self) -> Result<(), ()> { + fn wait_power_ready(&mut self) -> impl Future> { poll_fn(move |cx| { POWER_WAKER.register(cx.waker()); @@ -172,6 +174,5 @@ impl VbusDetect for &SoftwareVbusDetect { Poll::Pending } }) - .await } } diff --git a/embassy-nrf/src/wdt.rs b/embassy-nrf/src/wdt.rs index e4cfa3344..308071726 100644 --- a/embassy-nrf/src/wdt.rs +++ b/embassy-nrf/src/wdt.rs @@ -3,8 +3,15 @@ //! This HAL implements a basic watchdog timer with 1..=8 handles. //! Once the watchdog has been started, it cannot be stopped. -use crate::pac::WDT; -use crate::peripherals; +#![macro_use] + +use core::hint::unreachable_unchecked; + +use embassy_hal_internal::PeripheralType; + +use crate::pac::wdt::vals; +pub use crate::pac::wdt::vals::{Halt as HaltConfig, Sleep as SleepConfig}; +use crate::{interrupt, pac, peripherals, Peri}; const MIN_TICKS: u32 = 15; @@ -18,29 +25,29 @@ pub struct Config { pub timeout_ticks: u32, /// Should the watchdog continue to count during sleep modes? - pub run_during_sleep: bool, + pub action_during_sleep: SleepConfig, /// Should the watchdog continue to count when the CPU is halted for debug? - pub run_during_debug_halt: bool, + pub action_during_debug_halt: HaltConfig, } impl Config { /// Create a config structure from the current configuration of the WDT /// peripheral. - pub fn try_new(_wdt: &peripherals::WDT) -> Option { - let r = unsafe { &*WDT::ptr() }; + pub fn try_new(_wdt: &Peri<'_, T>) -> Option { + let r = T::REGS; - #[cfg(not(feature = "_nrf91"))] - let runstatus = r.runstatus.read().runstatus().bit(); - #[cfg(feature = "_nrf91")] - let runstatus = r.runstatus.read().runstatuswdt().bit(); + #[cfg(not(any(feature = "_nrf91", feature = "_nrf5340")))] + let runstatus = r.runstatus().read().runstatus(); + #[cfg(any(feature = "_nrf91", feature = "_nrf5340"))] + let runstatus = r.runstatus().read().runstatuswdt(); if runstatus { - let config = r.config.read(); + let config = r.config().read(); Some(Self { - timeout_ticks: r.crv.read().bits(), - run_during_sleep: config.sleep().bit(), - run_during_debug_halt: config.halt().bit(), + timeout_ticks: r.crv().read(), + action_during_sleep: config.sleep(), + action_during_debug_halt: config.halt(), }) } else { None @@ -52,18 +59,18 @@ impl Default for Config { fn default() -> Self { Self { timeout_ticks: 32768, // 1 second - run_during_debug_halt: true, - run_during_sleep: true, + action_during_debug_halt: HaltConfig::RUN, + action_during_sleep: SleepConfig::RUN, } } } /// Watchdog driver. -pub struct Watchdog { - _private: (), +pub struct Watchdog { + _wdt: Peri<'static, T>, } -impl Watchdog { +impl Watchdog { /// Try to create a new watchdog driver. /// /// This function will return an error if the watchdog is already active @@ -73,49 +80,47 @@ impl Watchdog { /// `N` must be between 1 and 8, inclusive. #[inline] pub fn try_new( - wdt: peripherals::WDT, + wdt: Peri<'static, T>, config: Config, - ) -> Result<(Self, [WatchdogHandle; N]), peripherals::WDT> { + ) -> Result<(Self, [WatchdogHandle; N]), Peri<'static, T>> { assert!(N >= 1 && N <= 8); - let r = unsafe { &*WDT::ptr() }; + let r = T::REGS; let crv = config.timeout_ticks.max(MIN_TICKS); - let rren = (1u32 << N) - 1; + let rren = crate::pac::wdt::regs::Rren((1u32 << N) - 1); - #[cfg(not(feature = "_nrf91"))] - let runstatus = r.runstatus.read().runstatus().bit(); - #[cfg(feature = "_nrf91")] - let runstatus = r.runstatus.read().runstatuswdt().bit(); + #[cfg(not(any(feature = "_nrf91", feature = "_nrf5340")))] + let runstatus = r.runstatus().read().runstatus(); + #[cfg(any(feature = "_nrf91", feature = "_nrf5340"))] + let runstatus = r.runstatus().read().runstatuswdt(); if runstatus { - let curr_config = r.config.read(); - if curr_config.halt().bit() != config.run_during_debug_halt - || curr_config.sleep().bit() != config.run_during_sleep - || r.crv.read().bits() != crv - || r.rren.read().bits() != rren + let curr_config = r.config().read(); + if curr_config.halt() != config.action_during_debug_halt + || curr_config.sleep() != config.action_during_sleep + || r.crv().read() != crv + || r.rren().read() != rren { return Err(wdt); } } else { - r.config.write(|w| { - w.sleep().bit(config.run_during_sleep); - w.halt().bit(config.run_during_debug_halt); - w + r.config().write(|w| { + w.set_sleep(config.action_during_sleep); + w.set_halt(config.action_during_debug_halt); }); - r.intenset.write(|w| w.timeout().set_bit()); + r.intenset().write(|w| w.set_timeout(true)); - r.crv.write(|w| unsafe { w.bits(crv) }); - r.rren.write(|w| unsafe { w.bits(rren) }); - r.tasks_start.write(|w| unsafe { w.bits(1) }); + r.crv().write_value(crv); + r.rren().write_value(rren); + r.tasks_start().write_value(1); } - let this = Self { _private: () }; + let this = Self { _wdt: wdt }; - const DUMMY_HANDLE: WatchdogHandle = WatchdogHandle { index: 0 }; - let mut handles = [DUMMY_HANDLE; N]; + let mut handles = [const { WatchdogHandle { index: 0 } }; N]; for i in 0..N { - handles[i] = WatchdogHandle { index: i as u8 }; + handles[i] = unsafe { WatchdogHandle::steal::(i as u8) }; handles[i].pet(); } @@ -130,8 +135,7 @@ impl Watchdog { /// interrupt has been enabled. #[inline(always)] pub fn enable_interrupt(&mut self) { - let r = unsafe { &*WDT::ptr() }; - r.intenset.write(|w| w.timeout().set_bit()); + T::REGS.intenset().write(|w| w.set_timeout(true)); } /// Disable the watchdog interrupt. @@ -139,8 +143,7 @@ impl Watchdog { /// NOTE: This has no effect on the reset caused by the Watchdog. #[inline(always)] pub fn disable_interrupt(&mut self) { - let r = unsafe { &*WDT::ptr() }; - r.intenclr.write(|w| w.timeout().set_bit()); + T::REGS.intenclr().write(|w| w.set_timeout(true)); } /// Is the watchdog still awaiting pets from any handle? @@ -149,9 +152,9 @@ impl Watchdog { /// handles to prevent a reset this time period. #[inline(always)] pub fn awaiting_pets(&self) -> bool { - let r = unsafe { &*WDT::ptr() }; - let enabled = r.rren.read().bits(); - let status = r.reqstatus.read().bits(); + let r = T::REGS; + let enabled = r.rren().read().0; + let status = r.reqstatus().read().0; (status & enabled) == 0 } } @@ -162,6 +165,22 @@ pub struct WatchdogHandle { } impl WatchdogHandle { + fn regs(&self) -> pac::wdt::Wdt { + match self.index / 8 { + #[cfg(not(feature = "_multi_wdt"))] + peripherals::WDT::INDEX => peripherals::WDT::REGS, + #[cfg(feature = "_multi_wdt")] + peripherals::WDT0::INDEX => peripherals::WDT0::REGS, + #[cfg(feature = "_multi_wdt")] + peripherals::WDT1::INDEX => peripherals::WDT1::REGS, + _ => unsafe { unreachable_unchecked() }, + } + } + + fn rr_index(&self) -> usize { + usize::from(self.index % 8) + } + /// Pet the watchdog. /// /// This function pets the given watchdog handle. @@ -170,16 +189,14 @@ impl WatchdogHandle { /// prevent a reset from occurring. #[inline] pub fn pet(&mut self) { - let r = unsafe { &*WDT::ptr() }; - r.rr[self.index as usize].write(|w| w.rr().reload()); + let r = self.regs(); + r.rr(self.rr_index()).write(|w| w.set_rr(vals::Rr::RELOAD)); } /// Has this handle been pet within the current window? pub fn is_pet(&self) -> bool { - let r = unsafe { &*WDT::ptr() }; - let rd = r.reqstatus.read().bits(); - let idx = self.index as usize; - ((rd >> idx) & 0x1) == 0 + let r = self.regs(); + !r.reqstatus().read().rr(self.rr_index()) } /// Steal a watchdog handle by index. @@ -187,7 +204,33 @@ impl WatchdogHandle { /// # Safety /// Watchdog must be initialized and `index` must be between `0` and `N-1` /// where `N` is the handle count when initializing. - pub unsafe fn steal(index: u8) -> Self { - Self { index } + pub unsafe fn steal(index: u8) -> Self { + Self { + index: T::INDEX * 8 + index, + } } } + +pub(crate) trait SealedInstance { + const REGS: pac::wdt::Wdt; + const INDEX: u8; +} + +/// WDT instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_wdt { + ($type:ident, $pac_type:ident, $irq:ident, $index:literal) => { + impl crate::wdt::SealedInstance for peripherals::$type { + const REGS: pac::wdt::Wdt = pac::$pac_type; + const INDEX: u8 = $index; + } + impl crate::wdt::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy-nxp/Cargo.toml b/embassy-nxp/Cargo.toml new file mode 100644 index 000000000..426af06a0 --- /dev/null +++ b/embassy-nxp/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "embassy-nxp" +version = "0.1.0" +edition = "2021" + +[dependencies] +cortex-m = "0.7.7" +cortex-m-rt = "0.7.0" +critical-section = "1.1.2" +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +lpc55-pac = "0.5.0" +defmt = { version = "1", optional = true } + +[features] +default = ["rt"] +rt = ["lpc55-pac/rt"] + +## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. +defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt"] diff --git a/embassy-nxp/src/gpio.rs b/embassy-nxp/src/gpio.rs new file mode 100644 index 000000000..c7c78ce61 --- /dev/null +++ b/embassy-nxp/src/gpio.rs @@ -0,0 +1,354 @@ +use embassy_hal_internal::{impl_peripheral, PeripheralType}; + +use crate::pac_utils::*; +use crate::{peripherals, Peri}; + +pub(crate) fn init() { + // Enable clocks for GPIO, PINT, and IOCON + syscon_reg() + .ahbclkctrl0 + .modify(|_, w| w.gpio0().enable().gpio1().enable().mux().enable().iocon().enable()); +} + +/// The GPIO pin level for pins set on "Digital" mode. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Level { + /// Logical low. Corresponds to 0V. + Low, + /// Logical high. Corresponds to VDD. + High, +} + +/// Pull setting for a GPIO input set on "Digital" mode. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum Pull { + /// No pull. + None, + /// Internal pull-up resistor. + Up, + /// Internal pull-down resistor. + Down, +} + +/// The LPC55 boards have two GPIO banks, each with 32 pins. This enum represents the two banks. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Bank { + Bank0 = 0, + Bank1 = 1, +} + +/// GPIO output driver. Internally, this is a specialized [Flex] pin. +pub struct Output<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [initial output](Level). + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_output(); + let mut result = Self { pin }; + + match initial_output { + Level::High => result.set_high(), + Level::Low => result.set_low(), + }; + + result + } + + pub fn set_high(&mut self) { + gpio_reg().set[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + pub fn set_low(&mut self) { + gpio_reg().clr[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + pub fn toggle(&mut self) { + gpio_reg().not[self.pin.pin_bank() as usize].write(|w| unsafe { w.bits(self.pin.bit()) }) + } + + /// Get the current output level of the pin. Note that the value returned by this function is + /// the voltage level reported by the pin, not the value set by the output driver. + pub fn level(&self) -> Level { + let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits(); + if bits & self.pin.bit() != 0 { + Level::High + } else { + Level::Low + } + } +} + +/// GPIO input driver. Internally, this is a specialized [Flex] pin. +pub struct Input<'d> { + pub(crate) pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Pull]. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + let mut result = Self { pin }; + result.set_pull(pull); + + result + } + + /// Set the pull configuration for the pin. To disable the pull, use [Pull::None]. + pub fn set_pull(&mut self, pull: Pull) { + match_iocon!(register, iocon_reg(), self.pin.pin_bank(), self.pin.pin_number(), { + register.modify(|_, w| match pull { + Pull::None => w.mode().inactive(), + Pull::Up => w.mode().pull_up(), + Pull::Down => w.mode().pull_down(), + }); + }); + } + + /// Get the current input level of the pin. + pub fn read(&self) -> Level { + let bits = gpio_reg().pin[self.pin.pin_bank() as usize].read().bits(); + if bits & self.pin.bit() != 0 { + Level::High + } else { + Level::Low + } + } +} + +/// A flexible GPIO (digital mode) pin whose mode is not yet determined. Under the hood, this is a +/// reference to a type-erased pin called ["AnyPin"](AnyPin). +pub struct Flex<'d> { + pub(crate) pin: Peri<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// Note: you cannot assume that the pin will be in Digital mode after this call. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>) -> Self { + Self { pin: pin.into() } + } + + /// Get the bank of this pin. See also [Bank]. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::{Bank, Flex}; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_15); + /// + /// assert_eq!(pin.pin_bank(), Bank::Bank1); + /// ``` + pub fn pin_bank(&self) -> Bank { + self.pin.pin_bank() + } + + /// Get the number of this pin within its bank. See also [Bank]. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::Flex; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_15); + /// + /// assert_eq!(pin.pin_number(), 15 as u8); + /// ``` + pub fn pin_number(&self) -> u8 { + self.pin.pin_number() + } + + /// Get the bit mask for this pin. Useful for setting or clearing bits in a register. Note: + /// PIOx_0 is bit 0, PIOx_1 is bit 1, etc. + /// + /// # Example + /// + /// ``` + /// use embassy_nxp::gpio::Flex; + /// + /// let p = embassy_nxp::init(Default::default()); + /// let pin = Flex::new(p.PIO1_3); + /// + /// assert_eq!(pin.bit(), 0b0000_1000); + /// ``` + pub fn bit(&self) -> u32 { + 1 << self.pin.pin_number() + } + + /// Set the pin to digital mode. This is required for using a pin as a GPIO pin. The default + /// setting for pins is (usually) non-digital. + fn set_as_digital(&mut self) { + match_iocon!(register, iocon_reg(), self.pin_bank(), self.pin_number(), { + register.modify(|_, w| w.digimode().digital()); + }); + } + + /// Set the pin in output mode. This implies setting the pin to digital mode, which this + /// function handles itself. + pub fn set_as_output(&mut self) { + self.set_as_digital(); + gpio_reg().dirset[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirsetp().bits(self.bit()) }) + } + + pub fn set_as_input(&mut self) { + self.set_as_digital(); + gpio_reg().dirclr[self.pin.pin_bank() as usize].write(|w| unsafe { w.dirclrp().bits(self.bit()) }) + } +} + +/// Sealed trait for pins. This trait is sealed and cannot be implemented outside of this crate. +pub(crate) trait SealedPin: Sized { + fn pin_bank(&self) -> Bank; + fn pin_number(&self) -> u8; +} + +/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an +/// [AnyPin]. By default, this trait is sealed and cannot be implemented outside of the +/// `embassy-nxp` crate due to the [SealedPin] trait. +#[allow(private_bounds)] +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self.pin_number() + } + + /// Returns the bank of this pin + #[inline] + fn bank(&self) -> Bank { + self.pin_bank() + } +} + +/// Type-erased GPIO pin. +pub struct AnyPin { + pin_bank: Bank, + pin_number: 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: Bank, pin_number: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_bank, pin_number }) + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + #[inline] + fn pin_bank(&self) -> Bank { + self.pin_bank + } + + #[inline] + fn pin_number(&self) -> u8 { + self.pin_number + } +} + +macro_rules! impl_pin { + ($name:ident, $bank:expr, $pin_num:expr) => { + impl Pin for peripherals::$name {} + impl SealedPin for peripherals::$name { + #[inline] + fn pin_bank(&self) -> Bank { + $bank + } + + #[inline] + fn pin_number(&self) -> u8 { + $pin_num + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$name) -> Self { + Self { + pin_bank: val.pin_bank(), + pin_number: val.pin_number(), + } + } + } + }; +} + +impl_pin!(PIO0_0, Bank::Bank0, 0); +impl_pin!(PIO0_1, Bank::Bank0, 1); +impl_pin!(PIO0_2, Bank::Bank0, 2); +impl_pin!(PIO0_3, Bank::Bank0, 3); +impl_pin!(PIO0_4, Bank::Bank0, 4); +impl_pin!(PIO0_5, Bank::Bank0, 5); +impl_pin!(PIO0_6, Bank::Bank0, 6); +impl_pin!(PIO0_7, Bank::Bank0, 7); +impl_pin!(PIO0_8, Bank::Bank0, 8); +impl_pin!(PIO0_9, Bank::Bank0, 9); +impl_pin!(PIO0_10, Bank::Bank0, 10); +impl_pin!(PIO0_11, Bank::Bank0, 11); +impl_pin!(PIO0_12, Bank::Bank0, 12); +impl_pin!(PIO0_13, Bank::Bank0, 13); +impl_pin!(PIO0_14, Bank::Bank0, 14); +impl_pin!(PIO0_15, Bank::Bank0, 15); +impl_pin!(PIO0_16, Bank::Bank0, 16); +impl_pin!(PIO0_17, Bank::Bank0, 17); +impl_pin!(PIO0_18, Bank::Bank0, 18); +impl_pin!(PIO0_19, Bank::Bank0, 19); +impl_pin!(PIO0_20, Bank::Bank0, 20); +impl_pin!(PIO0_21, Bank::Bank0, 21); +impl_pin!(PIO0_22, Bank::Bank0, 22); +impl_pin!(PIO0_23, Bank::Bank0, 23); +impl_pin!(PIO0_24, Bank::Bank0, 24); +impl_pin!(PIO0_25, Bank::Bank0, 25); +impl_pin!(PIO0_26, Bank::Bank0, 26); +impl_pin!(PIO0_27, Bank::Bank0, 27); +impl_pin!(PIO0_28, Bank::Bank0, 28); +impl_pin!(PIO0_29, Bank::Bank0, 29); +impl_pin!(PIO0_30, Bank::Bank0, 30); +impl_pin!(PIO0_31, Bank::Bank0, 31); +impl_pin!(PIO1_0, Bank::Bank1, 0); +impl_pin!(PIO1_1, Bank::Bank1, 1); +impl_pin!(PIO1_2, Bank::Bank1, 2); +impl_pin!(PIO1_3, Bank::Bank1, 3); +impl_pin!(PIO1_4, Bank::Bank1, 4); +impl_pin!(PIO1_5, Bank::Bank1, 5); +impl_pin!(PIO1_6, Bank::Bank1, 6); +impl_pin!(PIO1_7, Bank::Bank1, 7); +impl_pin!(PIO1_8, Bank::Bank1, 8); +impl_pin!(PIO1_9, Bank::Bank1, 9); +impl_pin!(PIO1_10, Bank::Bank1, 10); +impl_pin!(PIO1_11, Bank::Bank1, 11); +impl_pin!(PIO1_12, Bank::Bank1, 12); +impl_pin!(PIO1_13, Bank::Bank1, 13); +impl_pin!(PIO1_14, Bank::Bank1, 14); +impl_pin!(PIO1_15, Bank::Bank1, 15); +impl_pin!(PIO1_16, Bank::Bank1, 16); +impl_pin!(PIO1_17, Bank::Bank1, 17); +impl_pin!(PIO1_18, Bank::Bank1, 18); +impl_pin!(PIO1_19, Bank::Bank1, 19); +impl_pin!(PIO1_20, Bank::Bank1, 20); +impl_pin!(PIO1_21, Bank::Bank1, 21); +impl_pin!(PIO1_22, Bank::Bank1, 22); +impl_pin!(PIO1_23, Bank::Bank1, 23); +impl_pin!(PIO1_24, Bank::Bank1, 24); +impl_pin!(PIO1_25, Bank::Bank1, 25); +impl_pin!(PIO1_26, Bank::Bank1, 26); +impl_pin!(PIO1_27, Bank::Bank1, 27); +impl_pin!(PIO1_28, Bank::Bank1, 28); +impl_pin!(PIO1_29, Bank::Bank1, 29); +impl_pin!(PIO1_30, Bank::Bank1, 30); +impl_pin!(PIO1_31, Bank::Bank1, 31); diff --git a/embassy-nxp/src/lib.rs b/embassy-nxp/src/lib.rs new file mode 100644 index 000000000..ad2056c06 --- /dev/null +++ b/embassy-nxp/src/lib.rs @@ -0,0 +1,95 @@ +#![no_std] + +pub mod gpio; +mod pac_utils; +pub mod pint; + +pub use embassy_hal_internal::Peri; +pub use lpc55_pac as pac; + +/// Initialize the `embassy-nxp` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once and at startup, otherwise it panics. +pub fn init(_config: config::Config) -> Peripherals { + gpio::init(); + pint::init(); + + crate::Peripherals::take() +} + +embassy_hal_internal::peripherals! { + // External pins. These are not only GPIOs, they are multi-purpose pins and can be used by other + // peripheral types (e.g. I2C). + PIO0_0, + PIO0_1, + PIO0_2, + PIO0_3, + PIO0_4, + PIO0_5, + PIO0_6, + PIO0_7, + PIO0_8, + PIO0_9, + PIO0_10, + PIO0_11, + PIO0_12, + PIO0_13, + PIO0_14, + PIO0_15, + PIO0_16, + PIO0_17, + PIO0_18, + PIO0_19, + PIO0_20, + PIO0_21, + PIO0_22, + PIO0_23, + PIO0_24, + PIO0_25, + PIO0_26, + PIO0_27, + PIO0_28, + PIO0_29, + PIO0_30, + PIO0_31, + PIO1_0, + PIO1_1, + PIO1_2, + PIO1_3, + PIO1_4, + PIO1_5, + PIO1_6, + PIO1_7, + PIO1_8, + PIO1_9, + PIO1_10, + PIO1_11, + PIO1_12, + PIO1_13, + PIO1_14, + PIO1_15, + PIO1_16, + PIO1_17, + PIO1_18, + PIO1_19, + PIO1_20, + PIO1_21, + PIO1_22, + PIO1_23, + PIO1_24, + PIO1_25, + PIO1_26, + PIO1_27, + PIO1_28, + PIO1_29, + PIO1_30, + PIO1_31, +} + +/// HAL configuration for the NXP board. +pub mod config { + #[derive(Default)] + pub struct Config {} +} diff --git a/embassy-nxp/src/pac_utils.rs b/embassy-nxp/src/pac_utils.rs new file mode 100644 index 000000000..86a807f6c --- /dev/null +++ b/embassy-nxp/src/pac_utils.rs @@ -0,0 +1,323 @@ +/// Get the GPIO register block. This is used to configure all GPIO pins. +/// +/// # Safety +/// Due to the type system of peripherals, access to the settings of a single pin is possible only +/// by a single thread at a time. Read/Write operations on a single registers are NOT atomic. You +/// must ensure that the GPIO registers are not accessed concurrently by multiple threads. +pub(crate) fn gpio_reg() -> &'static lpc55_pac::gpio::RegisterBlock { + unsafe { &*lpc55_pac::GPIO::ptr() } +} + +/// Get the IOCON register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn iocon_reg() -> &'static lpc55_pac::iocon::RegisterBlock { + unsafe { &*lpc55_pac::IOCON::ptr() } +} + +/// Get the INPUTMUX register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn inputmux_reg() -> &'static lpc55_pac::inputmux::RegisterBlock { + unsafe { &*lpc55_pac::INPUTMUX::ptr() } +} + +/// Get the SYSCON register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn syscon_reg() -> &'static lpc55_pac::syscon::RegisterBlock { + unsafe { &*lpc55_pac::SYSCON::ptr() } +} + +/// Get the PINT register block. +/// +/// # Safety +/// Read/Write operations on a single registers are NOT atomic. You must ensure that the GPIO +/// registers are not accessed concurrently by multiple threads. +pub(crate) fn pint_reg() -> &'static lpc55_pac::pint::RegisterBlock { + unsafe { &*lpc55_pac::PINT::ptr() } +} + +/// Match the pin bank and number of a pin to the corresponding IOCON register. +/// +/// # Example +/// ``` +/// use embassy_nxp::gpio::Bank; +/// use embassy_nxp::pac_utils::{iocon_reg, match_iocon}; +/// +/// // Make pin PIO1_6 digital and set it to pull-down mode. +/// match_iocon!(register, iocon_reg(), Bank::Bank1, 6, { +/// register.modify(|_, w| w.mode().pull_down().digimode().digital()); +/// }); +/// ``` +macro_rules! match_iocon { + ($register:ident, $iocon_register:expr, $pin_bank:expr, $pin_number:expr, $action:expr) => { + match ($pin_bank, $pin_number) { + (Bank::Bank0, 0) => { + let $register = &($iocon_register).pio0_0; + $action; + } + (Bank::Bank0, 1) => { + let $register = &($iocon_register).pio0_1; + $action; + } + (Bank::Bank0, 2) => { + let $register = &($iocon_register).pio0_2; + $action; + } + (Bank::Bank0, 3) => { + let $register = &($iocon_register).pio0_3; + $action; + } + (Bank::Bank0, 4) => { + let $register = &($iocon_register).pio0_4; + $action; + } + (Bank::Bank0, 5) => { + let $register = &($iocon_register).pio0_5; + $action; + } + (Bank::Bank0, 6) => { + let $register = &($iocon_register).pio0_6; + $action; + } + (Bank::Bank0, 7) => { + let $register = &($iocon_register).pio0_7; + $action; + } + (Bank::Bank0, 8) => { + let $register = &($iocon_register).pio0_8; + $action; + } + (Bank::Bank0, 9) => { + let $register = &($iocon_register).pio0_9; + $action; + } + (Bank::Bank0, 10) => { + let $register = &($iocon_register).pio0_10; + $action; + } + (Bank::Bank0, 11) => { + let $register = &($iocon_register).pio0_11; + $action; + } + (Bank::Bank0, 12) => { + let $register = &($iocon_register).pio0_12; + $action; + } + (Bank::Bank0, 13) => { + let $register = &($iocon_register).pio0_13; + $action; + } + (Bank::Bank0, 14) => { + let $register = &($iocon_register).pio0_14; + $action; + } + (Bank::Bank0, 15) => { + let $register = &($iocon_register).pio0_15; + $action; + } + (Bank::Bank0, 16) => { + let $register = &($iocon_register).pio0_16; + $action; + } + (Bank::Bank0, 17) => { + let $register = &($iocon_register).pio0_17; + $action; + } + (Bank::Bank0, 18) => { + let $register = &($iocon_register).pio0_18; + $action; + } + (Bank::Bank0, 19) => { + let $register = &($iocon_register).pio0_19; + $action; + } + (Bank::Bank0, 20) => { + let $register = &($iocon_register).pio0_20; + $action; + } + (Bank::Bank0, 21) => { + let $register = &($iocon_register).pio0_21; + $action; + } + (Bank::Bank0, 22) => { + let $register = &($iocon_register).pio0_22; + $action; + } + (Bank::Bank0, 23) => { + let $register = &($iocon_register).pio0_23; + $action; + } + (Bank::Bank0, 24) => { + let $register = &($iocon_register).pio0_24; + $action; + } + (Bank::Bank0, 25) => { + let $register = &($iocon_register).pio0_25; + $action; + } + (Bank::Bank0, 26) => { + let $register = &($iocon_register).pio0_26; + $action; + } + (Bank::Bank0, 27) => { + let $register = &($iocon_register).pio0_27; + $action; + } + (Bank::Bank0, 28) => { + let $register = &($iocon_register).pio0_28; + $action; + } + (Bank::Bank0, 29) => { + let $register = &($iocon_register).pio0_29; + $action; + } + (Bank::Bank0, 30) => { + let $register = &($iocon_register).pio0_30; + $action; + } + (Bank::Bank0, 31) => { + let $register = &($iocon_register).pio0_31; + $action; + } + (Bank::Bank1, 0) => { + let $register = &($iocon_register).pio1_0; + $action; + } + (Bank::Bank1, 1) => { + let $register = &($iocon_register).pio1_1; + $action; + } + (Bank::Bank1, 2) => { + let $register = &($iocon_register).pio1_2; + $action; + } + (Bank::Bank1, 3) => { + let $register = &($iocon_register).pio1_3; + $action; + } + (Bank::Bank1, 4) => { + let $register = &($iocon_register).pio1_4; + $action; + } + (Bank::Bank1, 5) => { + let $register = &($iocon_register).pio1_5; + $action; + } + (Bank::Bank1, 6) => { + let $register = &($iocon_register).pio1_6; + $action; + } + (Bank::Bank1, 7) => { + let $register = &($iocon_register).pio1_7; + $action; + } + (Bank::Bank1, 8) => { + let $register = &($iocon_register).pio1_8; + $action; + } + (Bank::Bank1, 9) => { + let $register = &($iocon_register).pio1_9; + $action; + } + (Bank::Bank1, 10) => { + let $register = &($iocon_register).pio1_10; + $action; + } + (Bank::Bank1, 11) => { + let $register = &($iocon_register).pio1_11; + $action; + } + (Bank::Bank1, 12) => { + let $register = &($iocon_register).pio1_12; + $action; + } + (Bank::Bank1, 13) => { + let $register = &($iocon_register).pio1_13; + $action; + } + (Bank::Bank1, 14) => { + let $register = &($iocon_register).pio1_14; + $action; + } + (Bank::Bank1, 15) => { + let $register = &($iocon_register).pio1_15; + $action; + } + (Bank::Bank1, 16) => { + let $register = &($iocon_register).pio1_16; + $action; + } + (Bank::Bank1, 17) => { + let $register = &($iocon_register).pio1_17; + $action; + } + (Bank::Bank1, 18) => { + let $register = &($iocon_register).pio1_18; + $action; + } + (Bank::Bank1, 19) => { + let $register = &($iocon_register).pio1_19; + $action; + } + (Bank::Bank1, 20) => { + let $register = &($iocon_register).pio1_20; + $action; + } + (Bank::Bank1, 21) => { + let $register = &($iocon_register).pio1_21; + $action; + } + (Bank::Bank1, 22) => { + let $register = &($iocon_register).pio1_22; + $action; + } + (Bank::Bank1, 23) => { + let $register = &($iocon_register).pio1_23; + $action; + } + (Bank::Bank1, 24) => { + let $register = &($iocon_register).pio1_24; + $action; + } + (Bank::Bank1, 25) => { + let $register = &($iocon_register).pio1_25; + $action; + } + (Bank::Bank1, 26) => { + let $register = &($iocon_register).pio1_26; + $action; + } + (Bank::Bank1, 27) => { + let $register = &($iocon_register).pio1_27; + $action; + } + (Bank::Bank1, 28) => { + let $register = &($iocon_register).pio1_28; + $action; + } + (Bank::Bank1, 29) => { + let $register = &($iocon_register).pio1_29; + $action; + } + (Bank::Bank1, 30) => { + let $register = &($iocon_register).pio1_30; + $action; + } + (Bank::Bank1, 31) => { + let $register = &($iocon_register).pio1_31; + $action; + } + _ => unreachable!(), + } + }; +} + +pub(crate) use match_iocon; diff --git a/embassy-nxp/src/pint.rs b/embassy-nxp/src/pint.rs new file mode 100644 index 000000000..8d6dc1277 --- /dev/null +++ b/embassy-nxp/src/pint.rs @@ -0,0 +1,442 @@ +//! Pin Interrupt module. +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use critical_section::Mutex; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{self, AnyPin, Level, SealedPin}; +use crate::pac::interrupt; +use crate::pac_utils::*; +use crate::Peri; + +struct PinInterrupt { + assigned: bool, + waker: AtomicWaker, + /// If true, the interrupt was triggered due to this PinInterrupt. This is used to determine if + /// an [InputFuture] should return Poll::Ready. + at_fault: bool, +} + +impl PinInterrupt { + pub fn interrupt_active(&self) -> bool { + self.assigned + } + + /// Mark the interrupt as assigned to a pin. + pub fn enable(&mut self) { + self.assigned = true; + self.at_fault = false; + } + + /// Mark the interrupt as available. + pub fn disable(&mut self) { + self.assigned = false; + self.at_fault = false; + } + + /// Returns true if the interrupt was triggered due to this PinInterrupt. + /// + /// If this function returns true, it will also reset the at_fault flag. + pub fn at_fault(&mut self) -> bool { + let val = self.at_fault; + self.at_fault = false; + val + } + + /// Set the at_fault flag to true. + pub fn fault(&mut self) { + self.at_fault = true; + } +} + +const INTERUPT_COUNT: usize = 8; +static PIN_INTERRUPTS: Mutex> = Mutex::new(RefCell::new( + [const { + PinInterrupt { + assigned: false, + waker: AtomicWaker::new(), + at_fault: false, + } + }; INTERUPT_COUNT], +)); + +fn next_available_interrupt() -> Option { + critical_section::with(|cs| { + for (i, pin_interrupt) in PIN_INTERRUPTS.borrow(cs).borrow().iter().enumerate() { + if !pin_interrupt.interrupt_active() { + return Some(i); + } + } + + None + }) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Edge { + Rising, + Falling, + Both, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum InterruptOn { + Level(Level), + Edge(Edge), +} + +pub(crate) fn init() { + syscon_reg().ahbclkctrl0.modify(|_, w| w.pint().enable()); + + // Enable interrupts + unsafe { + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT0); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT1); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT2); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT3); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT4); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT5); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT6); + crate::pac::NVIC::unmask(crate::pac::Interrupt::PIN_INT7); + }; +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + #[allow(dead_code)] + pin: Peri<'d, AnyPin>, + interrupt_number: usize, +} + +impl<'d> InputFuture<'d> { + /// Create a new input future. Returns None if all interrupts are in use. + fn new(pin: Peri<'d, impl gpio::Pin>, interrupt_on: InterruptOn) -> Option { + let pin = pin.into(); + let interrupt_number = next_available_interrupt()?; + + // Clear interrupt, just in case + pint_reg() + .rise + .write(|w| unsafe { w.rdet().bits(1 << interrupt_number) }); + pint_reg() + .fall + .write(|w| unsafe { w.fdet().bits(1 << interrupt_number) }); + + // Enable input multiplexing on pin interrupt register 0 for pin (32*bank + pin_number) + inputmux_reg().pintsel[interrupt_number] + .write(|w| unsafe { w.intpin().bits(32 * pin.pin_bank() as u8 + pin.pin_number()) }); + + match interrupt_on { + InterruptOn::Level(level) => { + // Set pin interrupt register to edge sensitive or level sensitive + // 0 = edge sensitive, 1 = level sensitive + pint_reg() + .isel + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << interrupt_number)) }); + + // Enable level interrupt. + // + // Note: Level sensitive interrupts are enabled by the same register as rising edge + // is activated. + + // 0 = no-op, 1 = enable + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + + // Set active level + match level { + Level::Low => { + // 0 = no-op, 1 = select LOW + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + } + Level::High => { + // 0 = no-op, 1 = select HIGH + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + } + } + } + InterruptOn::Edge(edge) => { + // Set pin interrupt register to edge sensitive or level sensitive + // 0 = edge sensitive, 1 = level sensitive + pint_reg() + .isel + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << interrupt_number)) }); + + // Enable rising/falling edge detection + match edge { + Edge::Rising => { + // 0 = no-op, 1 = enable rising edge + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + // 0 = no-op, 1 = disable falling edge + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + } + Edge::Falling => { + // 0 = no-op, 1 = enable falling edge + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + // 0 = no-op, 1 = disable rising edge + pint_reg() + .cienr + .write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) }); + } + Edge::Both => { + // 0 = no-op, 1 = enable + pint_reg() + .sienr + .write(|w| unsafe { w.setenrl().bits(1 << interrupt_number) }); + pint_reg() + .sienf + .write(|w| unsafe { w.setenaf().bits(1 << interrupt_number) }); + } + } + } + } + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.enable(); + }); + + Some(Self { pin, interrupt_number }) + } + + /// Returns true if the interrupt was triggered for this pin. + fn interrupt_triggered(&self) -> bool { + let interrupt_number = self.interrupt_number; + + // Initially, we determine if the interrupt was triggered by this InputFuture by checking + // the flags of the interrupt_number. However, by the time we get to this point, the + // interrupt may have been triggered again, so we needed to clear the cpu flags immediately. + // As a solution, we mark which [PinInterrupt] is responsible for the interrupt ("at fault") + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.at_fault() + }) + } +} + +impl<'d> Drop for InputFuture<'d> { + fn drop(&mut self) { + let interrupt_number = self.interrupt_number; + + // Disable pin interrupt + // 0 = no-op, 1 = disable + pint_reg() + .cienr + .write(|w| unsafe { w.cenrl().bits(1 << interrupt_number) }); + pint_reg() + .cienf + .write(|w| unsafe { w.cenaf().bits(1 << interrupt_number) }); + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.disable(); + }); + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let interrupt_number = self.interrupt_number; + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.waker.register(cx.waker()); + }); + + if self.interrupt_triggered() { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} + +fn handle_interrupt(interrupt_number: usize) { + pint_reg() + .rise + .write(|w| unsafe { w.rdet().bits(1 << interrupt_number) }); + pint_reg() + .fall + .write(|w| unsafe { w.fdet().bits(1 << interrupt_number) }); + + critical_section::with(|cs| { + let mut pin_interrupts = PIN_INTERRUPTS.borrow(cs).borrow_mut(); + let pin_interrupt = &mut pin_interrupts[interrupt_number]; + + pin_interrupt.fault(); + pin_interrupt.waker.wake(); + }); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT0() { + handle_interrupt(0); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT1() { + handle_interrupt(1); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT2() { + handle_interrupt(2); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT3() { + handle_interrupt(3); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT4() { + handle_interrupt(4); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT5() { + handle_interrupt(5); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT6() { + handle_interrupt(6); +} + +#[allow(non_snake_case)] +#[interrupt] +fn PIN_INT7() { + handle_interrupt(7); +} + +impl gpio::Flex<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + InputFuture::new(self.pin.reborrow(), InterruptOn::Edge(Edge::Both))?.await; + Some(()) + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + InputFuture::new(self.pin.reborrow(), InterruptOn::Edge(Edge::Falling))?.await; + Some(()) + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + InputFuture::new(self.pin.reborrow(), InterruptOn::Edge(Edge::Rising))?.await; + Some(()) + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + InputFuture::new(self.pin.reborrow(), InterruptOn::Level(Level::Low))?.await; + Some(()) + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + InputFuture::new(self.pin.reborrow(), InterruptOn::Level(Level::High))?.await; + Some(()) + } +} + +impl gpio::Input<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + self.pin.wait_for_any_edge().await + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + self.pin.wait_for_falling_edge().await + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + self.pin.wait_for_rising_edge().await + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + self.pin.wait_for_low().await + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + self.pin.wait_for_high().await + } +} + +impl gpio::Output<'_> { + /// Wait for a falling or rising edge on the pin. You can have at most 8 pins waiting. If you + /// try to wait for more than 8 pins, this function will return `None`. + pub async fn wait_for_any_edge(&mut self) -> Option<()> { + self.pin.wait_for_any_edge().await + } + + /// Wait for a falling edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_falling_edge(&mut self) -> Option<()> { + self.pin.wait_for_falling_edge().await + } + + /// Wait for a rising edge on the pin. You can have at most 8 pins waiting. If you try to wait + /// for more than 8 pins, this function will return `None`. + pub async fn wait_for_rising_edge(&mut self) -> Option<()> { + self.pin.wait_for_rising_edge().await + } + + /// Wait for a low level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_low(&mut self) -> Option<()> { + self.pin.wait_for_low().await + } + + /// Wait for a high level on the pin. You can have at most 8 pins waiting. If you try to wait for + /// more than 8 pins, this function will return `None`. + pub async fn wait_for_high(&mut self) -> Option<()> { + self.pin.wait_for_high().await + } +} diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md index 7eef64292..7ac0a47cb 100644 --- a/embassy-rp/CHANGELOG.md +++ b/embassy-rp/CHANGELOG.md @@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.4.0 - 2025-03-09 + +- Add PIO functions. ([#3857](https://github.com/embassy-rs/embassy/pull/3857)) + The functions added in this change are `get_addr` `get_tx_threshold`, `set_tx_threshold`, `get_rx_threshold`, `set_rx_threshold`, `set_thresholds`. +- Expose the watchdog reset reason. ([#3877](https://github.com/embassy-rs/embassy/pull/3877)) +- Update pio-rs, reexport, move instr methods to SM. ([#3865](https://github.com/embassy-rs/embassy/pull/3865)) +- rp235x: add ImageDef features. ([#3890](https://github.com/embassy-rs/embassy/pull/3890)) +- doc: Fix "the the" ([#3903](https://github.com/embassy-rs/embassy/pull/3903)) +- pio: Add access to DMA engine byte swapping ([#3935](https://github.com/embassy-rs/embassy/pull/3935)) +- Modify BufferedUart initialization to take pins before interrupts ([#3983](https://github.com/embassy-rs/embassy/pull/3983)) + +## 0.3.1 - 2025-02-06 + +Small release fixing a few gnarly bugs, upgrading is strongly recommended. + +- Fix a race condition in the time driver that could cause missed interrupts. ([#3758](https://github.com/embassy-rs/embassy/issues/3758), [#3763](https://github.com/embassy-rs/embassy/pull/3763)) +- rp235x: Make atomics work across cores. ([#3851](https://github.com/embassy-rs/embassy/pull/3851)) +- rp235x: add workaround "SIO spinlock stuck after reset" bug, same as RP2040 ([#3851](https://github.com/embassy-rs/embassy/pull/3851)) +- rp235x: Ensure core1 is reset if core0 resets. ([#3851](https://github.com/embassy-rs/embassy/pull/3851)) +- rp235xb: correct ADC channel numbers. ([#3823](https://github.com/embassy-rs/embassy/pull/3823)) +- rp235x: enable watchdog tick generator. ([#3777](https://github.com/embassy-rs/embassy/pull/3777)) +- Relax I2C address validity check to allow using 7-bit addresses that would be reserved for 10-bit addresses. ([#3809](https://github.com/embassy-rs/embassy/issues/3809), [#3810](https://github.com/embassy-rs/embassy/pull/3810)) + +## 0.3.0 - 2025-01-05 + +- Updated `embassy-time` to v0.4 +- Initial rp235x support +- Setup timer0 tick when initializing clocks +- Allow separate control of duty cycle for each channel in a pwm slice by splitting the Pwm driver. +- Implement `embedded_io::Write` for Uart<'d, T: Instance, Blocking> and UartTx<'d, T: Instance, Blocking> +- Add `set_pullup()` to OutputOpenDrain. + ## 0.2.0 - 2024-08-05 - Add read_to_break_with_count diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index d0612957f..5df9e154c 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -1,10 +1,10 @@ [package] name = "embassy-rp" -version = "0.2.0" +version = "0.4.0" edition = "2021" license = "MIT OR Apache-2.0" -description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 microcontroller" -keywords = ["embedded", "async", "raspberry-pi", "rp2040", "embedded-hal"] +description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 or RP235x microcontroller" +keywords = ["embedded", "async", "rp235x", "rp2040", "embedded-hal"] categories = ["embedded", "hardware-support", "no-std", "asynchronous"] repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/embassy-rp" @@ -20,11 +20,16 @@ flavors = [ ] [package.metadata.docs.rs] -features = ["defmt", "unstable-pac", "time-driver"] +# TODO: it's not GREAT to set a specific target, but docs.rs builds will fail otherwise +# for now, default to rp2040 +features = ["defmt", "unstable-pac", "time-driver", "rp2040"] [features] default = [ "rt" ] -## Enable the rt feature of [`rp-pac`](https://docs.rs/rp-pac). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization. + +## Enable the `rt` feature of [`rp-pac`](https://docs.rs/rp-pac). +## With `rt` enabled the PAC provides interrupt vectors instead of letting [`cortex-m-rt`](https://docs.rs/cortex-m-rt) do that. +## See for more info. rt = [ "rp-pac/rt" ] ## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. @@ -40,7 +45,7 @@ critical-section-impl = ["critical-section/restore-state-u8"] unstable-pac = [] ## Enable the timer for use with `embassy-time` with a 1MHz tick rate. -time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000"] +time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000", "dep:embassy-time-queue-utils"] ## Enable ROM function cache. This will store the address of a ROM function when first used, improving performance of subsequent calls. rom-func-cache = [] @@ -81,7 +86,7 @@ boot2-ram-memcpy = [] boot2-w25q080 = [] ## Use boot2 with support for Winbond W25X10CL SPI flash. boot2-w25x10cl = [] -## Have embassy not provide the boot2 so you can use your own. +## Have embassy-rp not provide the boot2 so you can use your own. ## Place your own in the ".boot2" section like: ## ``` ## #[link_section = ".boot2"] @@ -90,6 +95,29 @@ boot2-w25x10cl = [] ## ``` boot2-none = [] +#! ### Image Definition support +#! RP2350's internal bootloader will only execute firmware if it has a valid Image Definition. +#! There are other tags that can be used (for example, you can tag an image as "DATA") +#! but programs will want to have an exe header. "SECURE_EXE" is usually what you want. +#! Use imagedef-none if you want to configure the Image Definition manually +#! If none are selected, imagedef-secure-exe will be used + +## Image is executable and starts in Secure mode +imagedef-secure-exe = [] +## Image is executable and starts in Non-secure mode +imagedef-nonsecure-exe = [] + +## Have embassy-rp not provide the Image Definition so you can use your own. +## Place your own in the ".start_block" section like: +## ```ignore +## use embassy_rp::block::ImageDef; +## +## #[link_section = ".start_block"] +## #[used] +## static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); // Update this with your own implementation. +## ``` +imagedef-none = [] + ## Configure the hal for use with the rp2040 rp2040 = ["rp-pac/rp2040"] _rp235x = ["rp-pac/rp235x"] @@ -105,45 +133,49 @@ _test = [] ## ## Takes up a little flash space, but picotool can then report the name of your ## program and other details. -binary-info = [ "rt" ] +binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"] [dependencies] -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } -embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } +embassy-time = { version = "0.4.0", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } -embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } -embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal" } +embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } atomic-polyfill = "1.0.1" -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } -nb = "1.0.0" +nb = "1.1.0" cfg-if = "1.0.0" cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" -critical-section = "1.1" +critical-section = "1.2.0" chrono = { version = "0.4", default-features = false, optional = true } embedded-io = { version = "0.6.1" } embedded-io-async = { version = "0.6.1" } embedded-storage = { version = "0.3" } embedded-storage-async = { version = "0.4.1" } -rand_core = "0.6.4" -fixed = "1.23.1" +fixed = "1.28.0" -rp-pac = { git = "https://github.com/embassy-rs/rp-pac.git", rev = "a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c", feature = ["rt"] } +rp-pac = { version = "7.0.0" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-nb = { version = "1.0" } -pio-proc = {version= "0.2" } -pio = {version= "0.2.1" } +rand-core-06 = { package = "rand_core", version = "0.6" } +rand-core-09 = { package = "rand_core", version = "0.9" } + +pio = { version = "0.3" } rp2040-boot2 = "0.3" -document-features = "0.2.7" +document-features = "0.2.10" sha2-const-stable = "0.1" +rp-binary-info = { version = "0.1.0", optional = true } +smart-leds = "0.4.0" [dev-dependencies] -embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +embassy-executor = { version = "0.7.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } static_cell = { version = "2" } diff --git a/embassy-rp/LICENSE-APACHE b/embassy-rp/LICENSE-APACHE new file mode 100644 index 000000000..48be1263d --- /dev/null +++ b/embassy-rp/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embassy-rp/LICENSE-MIT b/embassy-rp/LICENSE-MIT new file mode 100644 index 000000000..f1cfbd5f7 --- /dev/null +++ b/embassy-rp/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embassy-rp/README.md b/embassy-rp/README.md index 16b189344..8e16184b8 100644 --- a/embassy-rp/README.md +++ b/embassy-rp/README.md @@ -2,7 +2,7 @@ HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. -The embassy-rp HAL targets the Raspberry Pi RP2040 microcontroller. The HAL implements both blocking and async APIs +The embassy-rp HAL targets the Raspberry Pi RP2040 as well as RP235x microcontroller. The HAL implements both blocking and async APIs for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to complete operations in low power mode and handling interrupts, so that applications can focus on more important matters. diff --git a/embassy-rp/build.rs b/embassy-rp/build.rs index 3216a3826..a8d387611 100644 --- a/embassy-rp/build.rs +++ b/embassy-rp/build.rs @@ -1,7 +1,8 @@ use std::env; +use std::ffi::OsStr; use std::fs::File; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; fn main() { if env::var("CARGO_FEATURE_RP2040").is_ok() { @@ -16,4 +17,23 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=link-rp.x.in"); } + + // code below taken from https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/build.rs + + let mut target = env::var("TARGET").unwrap(); + + // When using a custom target JSON, `$TARGET` contains the path to that JSON file. By + // convention, these files are named after the actual target triple, eg. + // `thumbv7m-customos-elf.json`, so we extract the file stem here to allow custom target specs. + let path = Path::new(&target); + if path.extension() == Some(OsStr::new("json")) { + target = path + .file_stem() + .map_or(target.clone(), |stem| stem.to_str().unwrap().to_string()); + } + + println!("cargo::rustc-check-cfg=cfg(has_fpu)"); + if target.ends_with("-eabihf") { + println!("cargo:rustc-cfg=has_fpu"); + } } diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index 9582e43c8..2db8e63d7 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -1,11 +1,10 @@ //! ADC driver. -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::mem; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin}; @@ -13,7 +12,7 @@ use crate::interrupt::typelevel::Binding; use crate::interrupt::InterruptExt; use crate::pac::dma::vals::TreqSel; use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; -use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt}; +use crate::{dma, interrupt, pac, peripherals, Peri, RegExt}; static WAKER: AtomicWaker = AtomicWaker::new(); @@ -22,9 +21,11 @@ static WAKER: AtomicWaker = AtomicWaker::new(); #[derive(Default)] pub struct Config {} +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] enum Source<'p> { - Pin(PeripheralRef<'p, AnyPin>), - TempSensor(PeripheralRef<'p, ADC_TEMP_SENSOR>), + Pin(Peri<'p, AnyPin>), + TempSensor(Peri<'p, ADC_TEMP_SENSOR>), } /// ADC channel. @@ -32,8 +33,7 @@ pub struct Channel<'p>(Source<'p>); impl<'p> Channel<'p> { /// Create a new ADC channel from pin with the provided [Pull] configuration. - pub fn new_pin(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { - into_ref!(pin); + pub fn new_pin(pin: Peri<'p, impl AdcPin + 'p>, pull: Pull) -> Self { pin.pad_ctrl().modify(|w| { #[cfg(feature = "_rp235x")] w.set_iso(false); @@ -47,22 +47,32 @@ impl<'p> Channel<'p> { w.set_pue(pull == Pull::Up); w.set_pde(pull == Pull::Down); }); - Self(Source::Pin(pin.map_into())) + Self(Source::Pin(pin.into())) } /// Create a new ADC channel for the internal temperature sensor. - pub fn new_temp_sensor(s: impl Peripheral

+ 'p) -> Self { + pub fn new_temp_sensor(s: Peri<'p, ADC_TEMP_SENSOR>) -> Self { let r = pac::ADC; r.cs().write_set(|w| w.set_ts_en(true)); - Self(Source::TempSensor(s.into_ref())) + Self(Source::TempSensor(s)) } fn channel(&self) -> u8 { + #[cfg(any(feature = "rp2040", feature = "rp235xa"))] + const CH_OFFSET: u8 = 26; + #[cfg(feature = "rp235xb")] + const CH_OFFSET: u8 = 40; + + #[cfg(any(feature = "rp2040", feature = "rp235xa"))] + const TS_CHAN: u8 = 4; + #[cfg(feature = "rp235xb")] + const TS_CHAN: u8 = 8; + match &self.0 { // this requires adc pins to be sequential and matching the adc channels, - // which is the case for rp2040 - Source::Pin(p) => p._pin() - 26, - Source::TempSensor(_) => 4, + // which is the case for rp2040/rp235xy + Source::Pin(p) => p._pin() - CH_OFFSET, + Source::TempSensor(_) => TS_CHAN, } } } @@ -180,7 +190,7 @@ impl<'d, M: Mode> Adc<'d, M> { impl<'d> Adc<'d, Async> { /// Create ADC driver in async mode. pub fn new( - _inner: impl Peripheral

+ 'd, + _inner: Peri<'d, ADC>, _irq: impl Binding, _config: Config, ) -> Self { @@ -193,18 +203,20 @@ impl<'d> Adc<'d, Async> { Self { phantom: PhantomData } } - async fn wait_for_ready() { + fn wait_for_ready() -> impl Future { let r = Self::regs(); - r.inte().write(|w| w.set_fifo(true)); - compiler_fence(Ordering::SeqCst); - poll_fn(|cx| { + + poll_fn(move |cx| { WAKER.register(cx.waker()); + + r.inte().write(|w| w.set_fifo(true)); + compiler_fence(Ordering::SeqCst); + if r.cs().read().ready() { return Poll::Ready(()); } Poll::Pending }) - .await; } /// Sample a value from a channel until completed. @@ -230,7 +242,7 @@ impl<'d> Adc<'d, Async> { buf: &mut [W], fcs_err: bool, div: u16, - dma: impl Peripheral

, + dma: Peri<'_, impl dma::Channel>, ) -> Result<(), Error> { #[cfg(feature = "rp2040")] let mut rrobin = 0_u8; @@ -311,7 +323,7 @@ impl<'d> Adc<'d, Async> { ch: &mut [Channel<'_>], buf: &mut [S], div: u16, - dma: impl Peripheral

, + dma: Peri<'_, impl dma::Channel>, ) -> Result<(), Error> { self.read_many_inner(ch.iter().map(|c| c.channel()), buf, false, div, dma) .await @@ -327,7 +339,7 @@ impl<'d> Adc<'d, Async> { ch: &mut [Channel<'_>], buf: &mut [Sample], div: u16, - dma: impl Peripheral

, + dma: Peri<'_, impl dma::Channel>, ) { // errors are reported in individual samples let _ = self @@ -350,7 +362,7 @@ impl<'d> Adc<'d, Async> { ch: &mut Channel<'_>, buf: &mut [S], div: u16, - dma: impl Peripheral

, + dma: Peri<'_, impl dma::Channel>, ) -> Result<(), Error> { self.read_many_inner([ch.channel()].into_iter(), buf, false, div, dma) .await @@ -365,7 +377,7 @@ impl<'d> Adc<'d, Async> { ch: &mut Channel<'_>, buf: &mut [Sample], div: u16, - dma: impl Peripheral

, + dma: Peri<'_, impl dma::Channel>, ) { // errors are reported in individual samples let _ = self @@ -382,7 +394,7 @@ impl<'d> Adc<'d, Async> { impl<'d> Adc<'d, Blocking> { /// Create ADC driver in blocking mode. - pub fn new_blocking(_inner: impl Peripheral

+ 'd, _config: Config) -> Self { + pub fn new_blocking(_inner: Peri<'d, ADC>, _config: Config) -> Self { Self::setup(); Self { phantom: PhantomData } diff --git a/embassy-rp/src/binary_info/consts.rs b/embassy-rp/src/binary_info/consts.rs deleted file mode 100644 index c8270c081..000000000 --- a/embassy-rp/src/binary_info/consts.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! Constants for binary info - -/// All Raspberry Pi specified IDs have this tag. -/// -/// You can create your own for custom fields. -pub const TAG_RASPBERRY_PI: u16 = super::make_tag(b"RP"); - -/// Used to note the program name - use with StringEntry -pub const ID_RP_PROGRAM_NAME: u32 = 0x02031c86; -/// Used to note the program version - use with StringEntry -pub const ID_RP_PROGRAM_VERSION_STRING: u32 = 0x11a9bc3a; -/// Used to note the program build date - use with StringEntry -pub const ID_RP_PROGRAM_BUILD_DATE_STRING: u32 = 0x9da22254; -/// Used to note the size of the binary - use with IntegerEntry -pub const ID_RP_BINARY_END: u32 = 0x68f465de; -/// Used to note a URL for the program - use with StringEntry -pub const ID_RP_PROGRAM_URL: u32 = 0x1856239a; -/// Used to note a description of the program - use with StringEntry -pub const ID_RP_PROGRAM_DESCRIPTION: u32 = 0xb6a07c19; -/// Used to note some feature of the program - use with StringEntry -pub const ID_RP_PROGRAM_FEATURE: u32 = 0xa1f4b453; -/// Used to note some whether this was a Debug or Release build - use with StringEntry -pub const ID_RP_PROGRAM_BUILD_ATTRIBUTE: u32 = 0x4275f0d3; -/// Used to note the Pico SDK version used - use with StringEntry -pub const ID_RP_SDK_VERSION: u32 = 0x5360b3ab; -/// Used to note which board this program targets - use with StringEntry -pub const ID_RP_PICO_BOARD: u32 = 0xb63cffbb; -/// Used to note which `boot2` image this program uses - use with StringEntry -pub const ID_RP_BOOT2_NAME: u32 = 0x7f8882e1; - -// End of file diff --git a/embassy-rp/src/binary_info/macros.rs b/embassy-rp/src/binary_info/macros.rs deleted file mode 100644 index 0d6ba5eb5..000000000 --- a/embassy-rp/src/binary_info/macros.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Handy macros for making Binary Info entries - -/// Generate a static item containing the given environment variable, -/// and return its [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_env { - ($tag:expr, $id:expr, $env_var_name:expr) => { - $crate::binary_info_str!($tag, $id, { - let value = concat!(env!($env_var_name), "\0"); - // # Safety - // - // We used `concat!` to null-terminate on the line above. - let value_cstr = unsafe { core::ffi::CStr::from_bytes_with_nul_unchecked(value.as_bytes()) }; - value_cstr - }) - }; -} - -/// Generate a static item containing the given string, and return its -/// [`EntryAddr`](super::EntryAddr). -/// -/// You must pass a numeric tag, a numeric ID, and `&CStr` (which is always -/// null-terminated). -#[macro_export] -macro_rules! binary_info_str { - ($tag:expr, $id:expr, $str:expr) => {{ - static ENTRY: $crate::binary_info::StringEntry = $crate::binary_info::StringEntry::new($tag, $id, $str); - ENTRY.addr() - }}; -} - -/// Generate a static item containing the given string, and return its -/// [`EntryAddr`](super::EntryAddr). -/// -/// You must pass a numeric tag, a numeric ID, and `&CStr` (which is always -/// null-terminated). -#[macro_export] -macro_rules! binary_info_int { - ($tag:expr, $id:expr, $int:expr) => {{ - static ENTRY: $crate::binary_info::IntegerEntry = $crate::binary_info::IntegerEntry::new($tag, $id, $int); - ENTRY.addr() - }}; -} - -/// Generate a static item containing the program name, and return its -/// [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_program_name { - ($name:expr) => { - $crate::binary_info_str!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_NAME, - $name - ) - }; -} - -/// Generate a static item containing the `CARGO_BIN_NAME` as the program name, -/// and return its [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_cargo_bin_name { - () => { - $crate::binary_info_env!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_NAME, - "CARGO_BIN_NAME" - ) - }; -} - -/// Generate a static item containing the program version, and return its -/// [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_program_version { - ($version:expr) => {{ - $crate::binary_info_str!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_VERSION, - $version - ) - }}; -} - -/// Generate a static item containing the `CARGO_PKG_VERSION` as the program -/// version, and return its [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_cargo_version { - () => { - $crate::binary_info_env!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_VERSION_STRING, - "CARGO_PKG_VERSION" - ) - }; -} - -/// Generate a static item containing the program URL, and return its -/// [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_program_url { - ($url:expr) => { - $crate::binary_info_str!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_URL, - $url - ) - }; -} - -/// Generate a static item containing the `CARGO_PKG_HOMEPAGE` as the program URL, -/// and return its [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_cargo_homepage_url { - () => { - $crate::binary_info_env!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_URL, - "CARGO_PKG_HOMEPAGE" - ) - }; -} - -/// Generate a static item containing the program description, and return its -/// [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_program_description { - ($description:expr) => { - $crate::binary_info_str!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_DESCRIPTION, - $description - ) - }; -} - -/// Generate a static item containing whether this is a debug or a release -/// build, and return its [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_program_build_attribute { - () => { - $crate::binary_info_str!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PROGRAM_BUILD_ATTRIBUTE, - { - if cfg!(debug_assertions) { - c"debug" - } else { - c"release" - } - } - ) - }; -} - -/// Generate a static item containing the specific board this program runs on, -/// and return its [`EntryAddr`](super::EntryAddr). -#[macro_export] -macro_rules! binary_info_rp_pico_board { - ($board:expr) => { - $crate::binary_info_str!( - $crate::binary_info::consts::TAG_RASPBERRY_PI, - $crate::binary_info::consts::ID_RP_PICO_BOARD, - $board - ) - }; -} - -// End of file diff --git a/embassy-rp/src/binary_info/mod.rs b/embassy-rp/src/binary_info/mod.rs deleted file mode 100644 index 213565cdf..000000000 --- a/embassy-rp/src/binary_info/mod.rs +++ /dev/null @@ -1,172 +0,0 @@ -//! Code and types for creating Picotool compatible "Binary Info" metadata -//! -//! Add something like this to your program, and compile with the "binary-info" -//! and "rt" features: -//! -//! ``` -//! #[link_section = ".bi_entries"] -//! #[used] -//! pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 3] = [ -//! embassy_rp::binary_info_rp_program_name!(c"Program Name Here"), -//! embassy_rp::binary_info_rp_cargo_version!(), -//! embassy_rp::binary_info_int!(embassy_rp::binary_info::make_tag(b"JP"), 0x0000_0001, 0x12345678), -//! ]; -//! ``` - -pub mod consts; - -mod types; -pub use types::*; - -#[macro_use] -mod macros; - -extern "C" { - /// The linker script sets this symbol to have the address of the first - /// entry in the `.bi_entries` section. - static __bi_entries_start: EntryAddr; - /// The linker script sets this symbol to have the address just past the - /// last entry in the `.bi_entries` section. - static __bi_entries_end: EntryAddr; - /// The linker script sets this symbol to have the address of the first - /// entry in the `.data` section. - static __sdata: u32; - /// The linker script sets this symbol to have the address just past the - /// first entry in the `.data` section. - static __edata: u32; - /// The linker script sets this symbol to have the address of the - /// initialisation data for the first entry in the `.data` section (i.e. a - /// flash address, not a RAM address). - static __sidata: u32; -} - -/// Picotool can find this block in our ELF file and report interesting -/// metadata. -/// -/// The data here tells picotool the start and end flash addresses of our -/// metadata. -#[cfg(feature = "binary-info")] -#[link_section = ".start_block"] -#[used] -pub static PICOTOOL_HEADER: Header = unsafe { - Header::new( - core::ptr::addr_of!(__bi_entries_start), - core::ptr::addr_of!(__bi_entries_end), - &MAPPING_TABLE, - ) -}; - -/// This tells picotool how to convert RAM addresses back into Flash addresses -#[cfg(feature = "binary-info")] -pub static MAPPING_TABLE: [MappingTableEntry; 2] = [ - // This is the entry for .data - MappingTableEntry { - source_addr_start: unsafe { core::ptr::addr_of!(__sidata) }, - dest_addr_start: unsafe { core::ptr::addr_of!(__sdata) }, - dest_addr_end: unsafe { core::ptr::addr_of!(__edata) }, - }, - // This is the terminating marker - MappingTableEntry::null(), -]; - -/// Create a 'Binary Info' entry containing the program name -/// -/// This is well-known to picotool, and will be displayed if you run `picotool info`. -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * ID: [`consts::ID_RP_PROGRAM_NAME`] -pub const fn rp_program_name(name: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_NAME, name) -} - -/// Create a 'Binary Info' entry containing the program version. -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_PROGRAM_VERSION_STRING`] -pub const fn rp_program_version(name: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_VERSION_STRING, name) -} - -/// Create a 'Binary Info' entry with a URL -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_PROGRAM_URL`] -pub const fn rp_program_url(url: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_URL, url) -} - -/// Create a 'Binary Info' with the program build date -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_PROGRAM_BUILD_DATE_STRING`] -pub const fn rp_program_build_date_string(value: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_BUILD_DATE_STRING, value) -} - -/// Create a 'Binary Info' with the size of the binary -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_BINARY_END`] -pub const fn rp_binary_end(value: u32) -> IntegerEntry { - IntegerEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_BINARY_END, value) -} - -/// Create a 'Binary Info' with a description of the program -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_PROGRAM_DESCRIPTION`] -pub const fn rp_program_description(value: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_DESCRIPTION, value) -} - -/// Create a 'Binary Info' with some feature of the program -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_PROGRAM_FEATURE`] -pub const fn rp_program_feature(value: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_FEATURE, value) -} - -/// Create a 'Binary Info' with some whether this was a Debug or Release build -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_PROGRAM_BUILD_ATTRIBUTE`] -pub const fn rp_program_build_attribute(value: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PROGRAM_BUILD_ATTRIBUTE, value) -} - -/// Create a 'Binary Info' with the Pico SDK version used -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_SDK_VERSION`] -pub const fn rp_sdk_version(value: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_SDK_VERSION, value) -} - -/// Create a 'Binary Info' with which board this program targets -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_PICO_BOARD`] -pub const fn rp_pico_board(value: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_PICO_BOARD, value) -} - -/// Create a 'Binary Info' with which `boot2` image this program uses -/// -/// * Tag: [`consts::TAG_RASPBERRY_PI`] -/// * Id: [`consts::ID_RP_BOOT2_NAME`] -pub const fn rp_boot2_name(value: &'static core::ffi::CStr) -> StringEntry { - StringEntry::new(consts::TAG_RASPBERRY_PI, consts::ID_RP_BOOT2_NAME, value) -} - -/// Create a tag from two ASCII letters. -/// -/// ``` -/// let tag = embassy_rp::binary_info::make_tag(b"RP"); -/// assert_eq!(tag, 0x5052); -/// ``` -pub const fn make_tag(c: &[u8; 2]) -> u16 { - u16::from_le_bytes(*c) -} - -// End of file diff --git a/embassy-rp/src/binary_info/types.rs b/embassy-rp/src/binary_info/types.rs deleted file mode 100644 index d2b192e32..000000000 --- a/embassy-rp/src/binary_info/types.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Types for the Binary Info system - -/// This is the 'Binary Info' header block that `picotool` looks for in your UF2 -/// file/ELF file/Pico in Bootloader Mode to give you useful metadata about your -/// program. -/// -/// It should be placed in the first 4096 bytes of flash, so use your `memory.x` -/// to insert a section between `.text` and `.vector_table` and put a static -/// value of this type in that section. -#[repr(C)] -pub struct Header { - /// Must be equal to Picotool::MARKER_START - marker_start: u32, - /// The first in our table of pointers to Entries - entries_start: *const EntryAddr, - /// The last in our table of pointers to Entries - entries_end: *const EntryAddr, - /// The first entry in a null-terminated RAM/Flash mapping table - mapping_table: *const MappingTableEntry, - /// Must be equal to Picotool::MARKER_END - marker_end: u32, -} - -impl Header { - /// This is the `BINARY_INFO_MARKER_START` magic value from `picotool` - const MARKER_START: u32 = 0x7188ebf2; - /// This is the `BINARY_INFO_MARKER_END` magic value from `picotool` - const MARKER_END: u32 = 0xe71aa390; - - /// Create a new `picotool` compatible header. - /// - /// * `entries_start` - the first [`EntryAddr`] in the table - /// * `entries_end` - the last [`EntryAddr`] in the table - /// * `mapping_table` - the RAM/Flash address mapping table - pub const fn new( - entries_start: *const EntryAddr, - entries_end: *const EntryAddr, - mapping_table: &'static [MappingTableEntry], - ) -> Self { - let mapping_table = mapping_table.as_ptr(); - Self { - marker_start: Self::MARKER_START, - entries_start, - entries_end, - mapping_table, - marker_end: Self::MARKER_END, - } - } -} - -// We need this as rustc complains that is is unsafe to share `*const u32` -// pointers between threads. We only allow these to be created with static -// data, so this is OK. -unsafe impl Sync for Header {} - -/// This is a reference to an entry. It's like a `&dyn` ref to some type `T: -/// Entry`, except that the run-time type information is encoded into the -/// Entry itself in very specific way. -#[repr(transparent)] -pub struct EntryAddr(*const u32); - -// We need this as rustc complains that is is unsafe to share `*const u32` -// pointers between threads. We only allow these to be created with static -// data, so this is OK. -unsafe impl Sync for EntryAddr {} - -/// Allows us to tell picotool where values are in the UF2 given their run-time -/// address. -/// -/// The most obvious example is RAM variables, which must be found in the -/// `.data` section of the UF2. -#[repr(C)] -pub struct MappingTableEntry { - /// The start address in RAM (or wherever the address picotool finds will - /// point) - pub source_addr_start: *const u32, - /// The start address in flash (or whever the data actually lives in the - /// ELF) - pub dest_addr_start: *const u32, - /// The end address in flash - pub dest_addr_end: *const u32, -} - -impl MappingTableEntry { - /// Generate a null entry to mark the end of the list - pub const fn null() -> MappingTableEntry { - MappingTableEntry { - source_addr_start: core::ptr::null(), - dest_addr_start: core::ptr::null(), - dest_addr_end: core::ptr::null(), - } - } -} - -// We need this as rustc complains that is is unsafe to share `*const u32` -// pointers between threads. We only allow these to be created with static -// data, so this is OK. -unsafe impl Sync for MappingTableEntry {} - -/// This is the set of data types that `picotool` supports. -#[repr(u16)] -pub enum DataType { - /// Raw data - Raw = 1, - /// Data with a size - SizedData = 2, - /// A list of binary data - BinaryInfoListZeroTerminated = 3, - /// A BSON encoded blob - Bson = 4, - /// An Integer with an ID - IdAndInt = 5, - /// A string with an Id - IdAndString = 6, - /// A block device - BlockDevice = 7, - /// GPIO pins, with their function - PinsWithFunction = 8, - /// GPIO pins, with their name - PinsWithName = 9, - /// GPIO pins, with multiple names? - PinsWithNames = 10, -} - -/// All Entries start with this common header -#[repr(C)] -struct EntryCommon { - data_type: DataType, - tag: u16, -} - -/// An entry which contains both an ID (e.g. `ID_RP_PROGRAM_NAME`) and a pointer -/// to a null-terminated string. -#[repr(C)] -pub struct StringEntry { - header: EntryCommon, - id: u32, - value: *const core::ffi::c_char, -} - -impl StringEntry { - /// Create a new `StringEntry` - pub const fn new(tag: u16, id: u32, value: &'static core::ffi::CStr) -> StringEntry { - StringEntry { - header: EntryCommon { - data_type: DataType::IdAndString, - tag, - }, - id, - value: value.as_ptr(), - } - } - - /// Get this entry's address - pub const fn addr(&self) -> EntryAddr { - EntryAddr(self as *const Self as *const u32) - } -} - -// We need this as rustc complains that is is unsafe to share `*const -// core::ffi::c_char` pointers between threads. We only allow these to be -// created with static string slices, so it's OK. -unsafe impl Sync for StringEntry {} - -/// An entry which contains both an ID (e.g. `ID_RP_BINARY_END`) and an integer. -#[repr(C)] -pub struct IntegerEntry { - header: EntryCommon, - id: u32, - value: u32, -} - -impl IntegerEntry { - /// Create a new `StringEntry` - pub const fn new(tag: u16, id: u32, value: u32) -> IntegerEntry { - IntegerEntry { - header: EntryCommon { - data_type: DataType::IdAndInt, - tag, - }, - id, - value, - } - } - - /// Get this entry's address - pub const fn addr(&self) -> EntryAddr { - EntryAddr(self as *const Self as *const u32) - } -} - -// End of file diff --git a/embassy-rp/src/block.rs b/embassy-rp/src/block.rs index d270bbf1c..a3e1ad925 100644 --- a/embassy-rp/src/block.rs +++ b/embassy-rp/src/block.rs @@ -6,6 +6,9 @@ //! firmware image. The `PARTITION_TABLE` Block (here the `PartitionTable` type) //! tells the ROM how to divide the flash space up into partitions. +// Credit: Taken from https://github.com/thejpster/rp-hal-rp2350-public (also licensed Apache 2.0 + MIT). +// Copyright (c) rp-rs organization + // These all have a 1 byte size /// An item ID for encoding a Vector Table address diff --git a/embassy-rp/src/bootsel.rs b/embassy-rp/src/bootsel.rs index d24ce7bd8..14f9e46aa 100644 --- a/embassy-rp/src/bootsel.rs +++ b/embassy-rp/src/bootsel.rs @@ -8,20 +8,19 @@ //! This module provides functionality to poll BOOTSEL from an embassy application. use crate::flash::in_ram; +use crate::Peri; -impl crate::peripherals::BOOTSEL { - /// Polls the BOOTSEL button. Returns true if the button is pressed. - /// - /// Polling isn't cheap, as this function waits for core 1 to finish it's current - /// task and for any DMAs from flash to complete - pub fn is_pressed(&mut self) -> bool { - let mut cs_status = Default::default(); +/// Reads the BOOTSEL button. Returns true if the button is pressed. +/// +/// Reading isn't cheap, as this function waits for core 1 to finish it's current +/// task and for any DMAs from flash to complete +pub fn is_bootsel_pressed(_p: Peri<'_, crate::peripherals::BOOTSEL>) -> bool { + let mut cs_status = Default::default(); - unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); + unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); - // bootsel is active low, so invert - !cs_status.infrompad() - } + // bootsel is active low, so invert + !cs_status.infrompad() } mod ram_helpers { diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 5f7ba10e2..d79bffab3 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -1,4 +1,67 @@ -//! Clock configuration for the RP2040 +//! # Clock configuration for the RP2040 and RP235x microcontrollers. +//! +//! # Clock Configuration API +//! +//! This module provides both high-level convenience functions and low-level manual +//! configuration options for the RP2040 clock system. +//! +//! ## High-Level Convenience Functions +//! +//! For most users, these functions provide an easy way to configure clocks: +//! +//! - `ClockConfig::system_freq(125_000_000)` - Set system clock to a specific frequency with automatic voltage scaling +//! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock +//! +//! ## Manual Configuration +//! +//! For advanced users who need precise control: +//! +//! ```rust,ignore +//! // Start with default configuration and customize it +//! let mut config = ClockConfig::default(); +//! +//! // Set custom PLL parameters +//! config.xosc = Some(XoscConfig { +//! hz: 12_000_000, +//! sys_pll: Some(PllConfig { +//! refdiv: 1, +//! fbdiv: 200, +//! post_div1: 6, +//! post_div2: 2, +//! }), +//! // ... other fields +//! }); +//! +//! // Set voltage for overclocking +//! config.core_voltage = CoreVoltage::V1_15; +//! ``` +//! +//! ## Examples +//! +//! ### Standard 125MHz (rp2040) or 150Mhz (rp235x) configuration +//! ```rust,ignore +//! let config = ClockConfig::crystal(12_000_000); +//! ``` +//! +//! Or using the default configuration: +//! ```rust,ignore +//! let config = ClockConfig::default(); +//! ``` +//! +//! ### Overclock to 200MHz +//! ```rust,ignore +//! let config = ClockConfig::system_freq(200_000_000); +//! ``` +//! +//! ### Manual configuration for advanced scenarios +//! ```rust,ignore +//! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, CoreVoltage}; +//! +//! // Start with defaults and customize +//! let mut config = ClockConfig::default(); +//! config.core_voltage = CoreVoltage::V1_15; +//! // Set other parameters as needed... +//! ``` #[cfg(feature = "rp2040")] use core::arch::asm; @@ -7,18 +70,30 @@ use core::marker::PhantomData; use core::sync::atomic::AtomicU16; use core::sync::atomic::{AtomicU32, Ordering}; -use embassy_hal_internal::{into_ref, PeripheralRef}; use pac::clocks::vals::*; use crate::gpio::{AnyPin, SealedPin}; #[cfg(feature = "rp2040")] use crate::pac::common::{Reg, RW}; -use crate::{pac, reset, Peripheral}; +use crate::{pac, reset, Peri}; // NOTE: all gpin handling is commented out for future reference. // gpin is not usually safe to use during the boot init() call, so it won't // be very useful until we have runtime clock reconfiguration. once this // happens we can resurrect the commented-out gpin bits. + +/// Clock error types. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockError { + /// PLL failed to lock within the timeout period. + PllLockTimedOut, + /// Could not find valid PLL parameters for system clock. + InvalidPllParameters, + /// Reading the core voltage failed due to an unexpected value in the register. + UnexpectedCoreVoltageRead, +} + struct Clocks { xosc: AtomicU32, sys: AtomicU32, @@ -27,6 +102,7 @@ struct Clocks { pll_usb: AtomicU32, usb: AtomicU32, adc: AtomicU32, + // See above re gpin handling being commented out // gpin0: AtomicU32, // gpin1: AtomicU32, rosc: AtomicU32, @@ -43,6 +119,7 @@ static CLOCKS: Clocks = Clocks { pll_usb: AtomicU32::new(0), usb: AtomicU32::new(0), adc: AtomicU32::new(0), + // See above re gpin handling being commented out // gpin0: AtomicU32::new(0), // gpin1: AtomicU32::new(0), rosc: AtomicU32::new(0), @@ -66,10 +143,132 @@ pub enum PeriClkSrc { Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , } +/// Core voltage regulator settings. +/// +/// The voltage regulator can be configured for different output voltages. +/// Higher voltages allow for higher clock frequencies but increase power consumption and heat. +#[cfg(feature = "rp2040")] +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreVoltage { + /// 0.80V + V0_80 = 0b0000, + /// 0.85V + V0_85 = 0b0110, + /// 0.90V + V0_90 = 0b0111, + /// 0.95V + V0_95 = 0b1000, + /// 1.00V + V1_00 = 0b1001, + /// 1.05V + V1_05 = 0b1010, + /// 1.10V - Default voltage level + V1_10 = 0b1011, + /// 1.15V - Required for overclocking to 133-200MHz + V1_15 = 0b1100, + /// 1.20V + V1_20 = 0b1101, + /// 1.25V + V1_25 = 0b1110, + /// 1.30V + V1_30 = 0b1111, +} + +/// Core voltage regulator settings. +/// +/// The voltage regulator can be configured for different output voltages. +/// Higher voltages allow for higher clock frequencies but increase power consumption and heat. +/// +/// **Note**: The maximum voltage is 1.30V, unless unlocked by setting unless the voltage limit +/// is disabled using the disable_voltage_limit field in the vreg_ctrl register. For lack of practical use at this +/// point in time, this is not implemented here. So the maximum voltage in this enum is 1.30V for now. +#[cfg(feature = "_rp235x")] +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreVoltage { + /// 0.55V + V0_55 = 0b00000, + /// 0.60V + V0_60 = 0b00001, + /// 0.65V + V0_65 = 0b00010, + /// 0.70V + V0_70 = 0b00011, + /// 0.75V + V0_75 = 0b00100, + /// 0.80V + V0_80 = 0b00101, + /// 0.85V + V0_85 = 0b00110, + /// 0.90V + V0_90 = 0b00111, + /// 0.95V + V0_95 = 0b01000, + /// 1.00V + V1_00 = 0b01001, + /// 1.05V + V1_05 = 0b01010, + /// 1.10V - Default voltage level + V1_10 = 0b01011, + /// 1.15V + V1_15 = 0b01100, + /// 1.20V + V1_20 = 0b01101, + /// 1.25V + V1_25 = 0b01110, + /// 1.30V + V1_30 = 0b01111, +} + +impl CoreVoltage { + /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. + /// Sets the BOD threshold to approximately 80% of the core voltage. + fn recommended_bod(self) -> u8 { + #[cfg(feature = "rp2040")] + match self { + CoreVoltage::V0_80 => 0b0100, // 0.645V (~81% of 0.80V) + CoreVoltage::V0_85 => 0b0101, // 0.688V (~81% of 0.85V) + CoreVoltage::V0_90 => 0b0110, // 0.731V (~81% of 0.90V) + CoreVoltage::V0_95 => 0b0111, // 0.774V (~81% of 0.95V) + CoreVoltage::V1_00 => 0b1000, // 0.817V (~82% of 1.00V) + CoreVoltage::V1_05 => 0b1000, // 0.817V (~78% of 1.05V) + CoreVoltage::V1_10 => 0b1001, // 0.860V (~78% of 1.10V), the default + CoreVoltage::V1_15 => 0b1010, // 0.903V (~79% of 1.15V) + CoreVoltage::V1_20 => 0b1011, // 0.946V (~79% of 1.20V) + CoreVoltage::V1_25 => 0b1100, // 0.989V (~79% of 1.25V) + CoreVoltage::V1_30 => 0b1101, // 1.032V (~79% of 1.30V) + } + #[cfg(feature = "_rp235x")] + match self { + CoreVoltage::V0_55 => 0b00001, // 0.516V (~94% of 0.55V) + CoreVoltage::V0_60 => 0b00010, // 0.559V (~93% of 0.60V) + CoreVoltage::V0_65 => 0b00011, // 0.602V (~93% of 0.65V) + CoreVoltage::V0_70 => 0b00011, // 0.602V (~86% of 0.70V) + CoreVoltage::V0_75 => 0b00100, // 0.645V (~86% of 0.75V) + CoreVoltage::V0_80 => 0b00101, // 0.688V (~86% of 0.80V) + CoreVoltage::V0_85 => 0b00110, // 0.731V (~86% of 0.85V) + CoreVoltage::V0_90 => 0b00110, // 0.731V (~81% of 0.90V) + CoreVoltage::V0_95 => 0b00111, // 0.774V (~81% of 0.95V) + CoreVoltage::V1_00 => 0b01000, // 0.817V (~82% of 1.00V) + CoreVoltage::V1_05 => 0b01000, // 0.817V (~78% of 1.05V) + CoreVoltage::V1_10 => 0b01001, // 0.860V (~78% of 1.10V), the default + CoreVoltage::V1_15 => 0b01001, // 0.860V (~75% of 1.15V) + CoreVoltage::V1_20 => 0b01010, // 0.903V (~75% of 1.20V) + CoreVoltage::V1_25 => 0b01010, // 0.903V (~72% of 1.25V) + CoreVoltage::V1_30 => 0b01011, // 0.946V (~73% of 1.30V) + // all others: 0.946V (see CoreVoltage: we do not support setting Voltages higher than 1.30V at this point) + } + } +} + /// CLock configuration. #[non_exhaustive] pub struct ClockConfig { @@ -90,12 +289,59 @@ pub struct ClockConfig { /// RTC clock configuration. #[cfg(feature = "rp2040")] pub rtc_clk: Option, + /// Core voltage scaling. Defaults to 1.10V. + pub core_voltage: CoreVoltage, + /// Voltage stabilization delay in microseconds. + /// If not set, defaults will be used based on voltage level. + pub voltage_stabilization_delay_us: Option, + // See above re gpin handling being commented out // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, } +impl Default for ClockConfig { + /// Creates a minimal default configuration with safe values. + /// + /// This configuration uses the ring oscillator (ROSC) as the clock source + /// and sets minimal defaults that guarantee a working system. It's intended + /// as a starting point for manual configuration. + /// + /// Most users should use one of the more specific configuration functions: + /// - `ClockConfig::crystal()` - Standard configuration with external crystal + /// - `ClockConfig::rosc()` - Configuration using only the internal oscillator + /// - `ClockConfig::system_freq()` - Configuration for a specific system frequency + fn default() -> Self { + Self { + rosc: None, + xosc: None, + ref_clk: RefClkConfig { + src: RefClkSrc::Rosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::Rosc, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: None, + usb_clk: None, + adc_clk: None, + #[cfg(feature = "rp2040")] + rtc_clk: None, + core_voltage: CoreVoltage::V1_10, + voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out + // gpin0: None, + // gpin1: None, + } + } +} + impl ClockConfig { /// Clock configuration derived from external crystal. + /// + /// This uses default settings for most parameters, suitable for typical use cases. + /// For manual control of PLL parameters, use `new_manual()` or modify the struct fields directly. pub fn crystal(crystal_hz: u32) -> Self { Self { rosc: Some(RoscConfig { @@ -109,7 +355,10 @@ impl ClockConfig { sys_pll: Some(PllConfig { refdiv: 1, fbdiv: 125, + #[cfg(feature = "rp2040")] post_div1: 6, + #[cfg(feature = "_rp235x")] + post_div1: 5, post_div2: 2, }), usb_pll: Some(PllConfig { @@ -150,6 +399,9 @@ impl ClockConfig { div_frac: 0, phase: 0, }), + core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) + voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out // gpin0: None, // gpin1: None, } @@ -190,26 +442,183 @@ impl ClockConfig { div_frac: 171, phase: 0, }), + core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) + voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out // gpin0: None, // gpin1: None, } } - // pub fn bind_gpin(&mut self, gpin: Gpin<'static, P>, hz: u32) { - // match P::NR { - // 0 => self.gpin0 = Some((hz, gpin.map_into())), - // 1 => self.gpin1 = Some((hz, gpin.map_into())), - // _ => unreachable!(), - // } - // // pin is now provisionally bound. if the config is applied it must be forgotten, - // // or Gpin::drop will deconfigure the clock input. - // } + /// Configure clocks derived from an external crystal with specific system frequency. + /// + /// This function calculates optimal PLL parameters to achieve the requested system + /// frequency. This only works for the usual 12MHz crystal. In case a different crystal is used, + /// You will have to set the PLL parameters manually. + /// + /// # Arguments + /// + /// * `sys_freq_hz` - The desired system clock frequency in Hz + /// + /// # Returns + /// + /// A ClockConfig configured to achieve the requested system frequency using the + /// the usual 12Mhz crystal, or an error if no valid parameters can be found. + /// + /// # Note on core voltage: + /// + /// **For RP2040**: + /// To date the only officially documented core voltages (see Datasheet section 2.15.3.1. Instances) are: + /// - Up to 133MHz: V1_10 (default) + /// - Above 133MHz: V1_15, but in the context of the datasheet covering reaching up to 200Mhz + /// That way all other frequencies below 133MHz or above 200MHz are not explicitly documented and not covered here. + /// In case You want to go below 133MHz or above 200MHz and want a different voltage, You will have to set that manually and with caution. + /// + /// **For RP235x**: + /// At this point in time there is no official manufacturer endorsement for running the chip on other core voltages and/or other clock speeds than the defaults. + /// Using this function is experimental and may not work as expected or even damage the chip. + /// + /// # Returns + /// + /// A Result containing either the configured ClockConfig or a ClockError. + pub fn system_freq(hz: u32) -> Result { + // Start with the standard configuration from crystal() + const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; + let mut config = Self::crystal(DEFAULT_CRYSTAL_HZ); + + // No need to modify anything if target frequency is already 125MHz + // (which is what crystal() configures by default) + #[cfg(feature = "rp2040")] + if hz == 125_000_000 { + return Ok(config); + } + #[cfg(feature = "_rp235x")] + if hz == 150_000_000 { + return Ok(config); + } + + // Find optimal PLL parameters for the requested frequency + let sys_pll_params = find_pll_params(DEFAULT_CRYSTAL_HZ, hz).ok_or(ClockError::InvalidPllParameters)?; + + // Replace the sys_pll configuration with our custom parameters + if let Some(xosc) = &mut config.xosc { + xosc.sys_pll = Some(sys_pll_params); + } + + // Set the voltage scale based on the target frequency + // Higher frequencies require higher voltage + #[cfg(feature = "rp2040")] + { + config.core_voltage = match hz { + freq if freq > 133_000_000 => CoreVoltage::V1_15, + _ => CoreVoltage::V1_10, // Use default voltage (V1_10) + }; + } + #[cfg(feature = "_rp235x")] + { + config.core_voltage = match hz { + // There is no official support for running the chip on other core voltages and/or other clock speeds than the defaults. + // So for now we have not way of knowing what the voltage should be. Change this if the manufacturer provides more information. + _ => CoreVoltage::V1_10, // Use default voltage (V1_10) + }; + } + + Ok(config) + } + + /// Configure with manual PLL settings for full control over system clock + /// + /// This method provides a simple way to configure the system with custom PLL parameters + /// without needing to understand the full nested configuration structure. + /// + /// # Arguments + /// + /// * `xosc_hz` - The frequency of the external crystal in Hz + /// * `pll_config` - The PLL configuration parameters to achieve desired frequency + /// * `core_voltage` - Voltage scaling for overclocking (required for >133MHz) + /// + /// # Returns + /// + /// A ClockConfig configured with the specified PLL parameters + /// + /// # Example + /// + /// ```rust,ignore + /// // Configure for 200MHz operation + /// let config = Config::default(); + /// config.clocks = ClockConfig::manual_pll( + /// 12_000_000, + /// PllConfig { + /// refdiv: 1, // Reference divider (12 MHz / 1 = 12 MHz) + /// fbdiv: 100, // Feedback divider (12 MHz * 100 = 1200 MHz VCO) + /// post_div1: 3, // First post divider (1200 MHz / 3 = 400 MHz) + /// post_div2: 2, // Second post divider (400 MHz / 2 = 200 MHz) + /// }, + /// CoreVoltage::V1_15 + /// ); + /// ``` + #[cfg(feature = "rp2040")] + pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, core_voltage: CoreVoltage) -> Self { + // Validate PLL parameters + assert!(pll_config.is_valid(xosc_hz), "Invalid PLL parameters"); + + let mut config = Self::default(); + + config.xosc = Some(XoscConfig { + hz: xosc_hz, + sys_pll: Some(pll_config), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + delay_multiplier: 128, + }); + + config.ref_clk = RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }; + + config.sys_clk = SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }; + + config.core_voltage = core_voltage; + config.peri_clk_src = Some(PeriClkSrc::Sys); + + // Set reasonable defaults for other clocks + config.usb_clk = Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }); + + config.adc_clk = Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }); + + config.rtc_clk = Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }); + + config + } } /// ROSC freq range. #[repr(u16)] #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RoscRange { /// Low range. Low = pac::rosc::vals::FreqRange::LOW.0, @@ -249,6 +658,7 @@ pub struct XoscConfig { } /// PLL configuration. +#[derive(Clone, Copy, Debug)] pub struct PllConfig { /// Reference divisor. pub refdiv: u8, @@ -260,6 +670,50 @@ pub struct PllConfig { pub post_div2: u8, } +impl PllConfig { + /// Calculate the output frequency for this PLL configuration + /// given an input frequency. + pub fn output_frequency(&self, input_hz: u32) -> u32 { + let ref_freq = input_hz / self.refdiv as u32; + let vco_freq = ref_freq * self.fbdiv as u32; + vco_freq / ((self.post_div1 * self.post_div2) as u32) + } + + /// Check if this PLL configuration is valid for the given input frequency. + pub fn is_valid(&self, input_hz: u32) -> bool { + // Check divisor constraints + if self.refdiv < 1 || self.refdiv > 63 { + return false; + } + if self.fbdiv < 16 || self.fbdiv > 320 { + return false; + } + if self.post_div1 < 1 || self.post_div1 > 7 { + return false; + } + if self.post_div2 < 1 || self.post_div2 > 7 { + return false; + } + if self.post_div2 > self.post_div1 { + return false; + } + + // Calculate reference frequency + let ref_freq = input_hz / self.refdiv as u32; + + // Check reference frequency range + if ref_freq < 5_000_000 || ref_freq > 800_000_000 { + return false; + } + + // Calculate VCO frequency + let vco_freq = ref_freq * self.fbdiv as u32; + + // Check VCO frequency range + vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000 + } +} + /// Reference clock config. pub struct RefClkConfig { /// Reference clock source. @@ -271,6 +725,7 @@ pub struct RefClkConfig { /// Reference clock source. #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RefClkSrc { /// XOSC. Xosc, @@ -278,6 +733,7 @@ pub enum RefClkSrc { Rosc, /// PLL USB. PllUsb, + // See above re gpin handling being commented out // Gpin0, // Gpin1, } @@ -285,6 +741,7 @@ pub enum RefClkSrc { /// SYS clock source. #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SysClkSrc { /// REF. Ref, @@ -296,6 +753,7 @@ pub enum SysClkSrc { Rosc, /// XOSC. Xosc, + // See above re gpin handling being commented out // Gpin0, // Gpin1, } @@ -322,6 +780,7 @@ pub struct SysClkConfig { #[repr(u8)] #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum UsbClkSrc { /// PLL USB. PllUsb = ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB as _, @@ -331,6 +790,7 @@ pub enum UsbClkSrc { Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , } @@ -349,6 +809,7 @@ pub struct UsbClkConfig { #[repr(u8)] #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AdcClkSrc { /// PLL USB. PllUsb = ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB as _, @@ -358,6 +819,7 @@ pub enum AdcClkSrc { Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , } @@ -376,6 +838,7 @@ pub struct AdcClkConfig { #[repr(u8)] #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg(feature = "rp2040")] pub enum RtcClkSrc { /// PLL USB. @@ -386,6 +849,7 @@ pub enum RtcClkSrc { Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, /// XOSC. Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , } @@ -403,6 +867,102 @@ pub struct RtcClkConfig { pub phase: u8, } +/// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency +/// based on the input frequency. +/// +/// This function searches for the best PLL configuration to achieve the requested target frequency +/// while staying within the VCO frequency range of 750MHz to 1800MHz. It prioritizes stability +/// over exact frequency matching by using larger divisors where possible. +/// +/// # Parameters +/// +/// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz for th most common one used on rp2040 boards) +/// * `target_hz`: The desired output frequency in Hz (e.g. 125MHz for standard RP2040 operation) +/// +/// # Returns +/// +/// * `Some(PllConfig)` if valid parameters were found +/// * `None` if no valid parameters could be found for the requested combination +/// +/// # Example +/// +/// ```rust,ignore +/// // Find parameters for 133MHz system clock from 12MHz crystal +/// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); +/// ``` +fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { + // Fixed reference divider for system PLL + const PLL_SYS_REFDIV: u8 = 1; + + // Calculate reference frequency + let reference_freq = input_hz as u64 / PLL_SYS_REFDIV as u64; + + // Start from highest fbdiv for better stability (like SDK does) + for fbdiv in (16..=320).rev() { + let vco_freq = reference_freq * fbdiv; + + // Check VCO frequency is within valid range + if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { + continue; + } + + // Try all possible postdiv combinations starting from larger values + // (more conservative/stable approach) + for post_div1 in (1..=7).rev() { + for post_div2 in (1..=post_div1).rev() { + let out_freq = vco_freq / (post_div1 * post_div2); + + // Check if we get the exact target frequency without remainder + if out_freq == target_hz as u64 && (vco_freq % (post_div1 * post_div2) == 0) { + return Some(PllConfig { + refdiv: PLL_SYS_REFDIV, + fbdiv: fbdiv as u16, + post_div1: post_div1 as u8, + post_div2: post_div2 as u8, + }); + } + } + } + } + + // If we couldn't find an exact match, find the closest match + let mut best_config = None; + let mut min_diff = u32::MAX; + + for fbdiv in (16..=320).rev() { + let vco_freq = reference_freq * fbdiv; + + if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { + continue; + } + + for post_div1 in (1..=7).rev() { + for post_div2 in (1..=post_div1).rev() { + let out_freq = (vco_freq / (post_div1 * post_div2) as u64) as u32; + let diff = if out_freq > target_hz { + out_freq - target_hz + } else { + target_hz - out_freq + }; + + // If this is closer to the target, save it + if diff < min_diff { + min_diff = diff; + best_config = Some(PllConfig { + refdiv: PLL_SYS_REFDIV, + fbdiv: fbdiv as u16, + post_div1: post_div1 as u8, + post_div2: post_div2 as u8, + }); + } + } + } + } + + // Return the closest match if we found one + best_config +} + /// safety: must be called exactly once at bootup pub(crate) unsafe fn init(config: ClockConfig) { // Reset everything except: @@ -445,6 +1005,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { reset::reset(peris); reset::unreset_wait(peris); + // See above re gpin handling being commented out // let gpin0_freq = config.gpin0.map_or(0, |p| { // core::mem::forget(p.1); // p.0 @@ -462,19 +1023,80 @@ pub(crate) unsafe fn init(config: ClockConfig) { }; CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); + // Set Core Voltage, if we have config for it and we're not using the default + { + let voltage = config.core_voltage; + + #[cfg(feature = "rp2040")] + let vreg = pac::VREG_AND_CHIP_RESET; + #[cfg(feature = "_rp235x")] + let vreg = pac::POWMAN; + + let current_vsel = vreg.vreg().read().vsel(); + let target_vsel = voltage as u8; + + // If the target voltage is different from the current one, we need to change it + if target_vsel != current_vsel { + // Set the voltage regulator to the target voltage + #[cfg(feature = "rp2040")] + vreg.vreg().modify(|w| w.set_vsel(target_vsel)); + #[cfg(feature = "_rp235x")] + // For rp235x changes to the voltage regulator are protected by a password, see datasheet section 6.4 Power Management (POWMAN) Registers + // The password is "5AFE" (0x5AFE), it must be set in the top 16 bits of the register + vreg.vreg().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | (0x5AFE << 16); // Set the password + w.set_vsel(target_vsel); + *w + }); + + // Wait for the voltage to stabilize. Use the provided delay or default based on voltage + let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| { + match voltage { + CoreVoltage::V1_15 => 1000, // 1ms for 1.15V + CoreVoltage::V1_20 | CoreVoltage::V1_25 | CoreVoltage::V1_30 => 2000, // 2ms for higher voltages + _ => 0, // no delay for all others + } + }); + + if settling_time_us != 0 { + // Delay in microseconds, using the ROSC frequency to calculate cycles + let cycles_per_us = rosc_freq / 1_000_000; + let delay_cycles = settling_time_us * cycles_per_us; + cortex_m::asm::delay(delay_cycles); + } + + // Only now set the BOD level. At this point the voltage is considered stable. + #[cfg(feature = "rp2040")] + vreg.bod().write(|w| { + w.set_vsel(voltage.recommended_bod()); + w.set_en(true); // Enable brownout detection + }); + #[cfg(feature = "_rp235x")] + vreg.bod().write(|w| { + w.0 = (w.0 & 0x0000FFFF) | (0x5AFE << 16); // Set the password + w.set_vsel(voltage.recommended_bod()); + w.set_en(true); // Enable brownout detection + }); + } + } + let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { Some(config) => { // start XOSC - // datasheet mentions support for clock inputs into XIN, but doesn't go into - // how this is achieved. pico-sdk doesn't support this at all. start_xosc(config.hz, config.delay_multiplier); let pll_sys_freq = match config.sys_pll { - Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), + Some(sys_pll_config) => match configure_pll(pac::PLL_SYS, config.hz, sys_pll_config) { + Ok(freq) => freq, + Err(e) => panic!("Failed to configure PLL_SYS: {:?}", e), + }, None => 0, }; let pll_usb_freq = match config.usb_pll { - Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), + Some(usb_pll_config) => match configure_pll(pac::PLL_USB, config.hz, usb_pll_config) { + Ok(freq) => freq, + Err(e) => panic!("Failed to configure PLL_USB: {:?}", e), + }, None => 0, }; @@ -482,6 +1104,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { } None => (0, 0, 0), }; + CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); @@ -494,6 +1117,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), + // See above re gpin handling being commented out // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), } @@ -512,12 +1136,23 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_int(config.ref_clk.div); }); - // Configure tick generation on the 2040. On the 2350 the timers are driven from the sysclk. + // Configure tick generation on the 2040. #[cfg(feature = "rp2040")] pac::WATCHDOG.tick().write(|w| { w.set_cycles((clk_ref_freq / 1_000_000) as u16); w.set_enable(true); }); + // Configure tick generator on the 2350 + #[cfg(feature = "_rp235x")] + { + let cycle_count = clk_ref_freq / 1_000_000; + + pac::TICKS.timer0_cycles().write(|w| w.0 = cycle_count); + pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true)); + + pac::TICKS.watchdog_cycles().write(|w| w.0 = cycle_count); + pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); + } let (sys_src, sys_aux, clk_sys_freq) = { use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; @@ -527,6 +1162,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), + // See above re gpin handling being commented out // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), }; @@ -570,6 +1206,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { PeriClkSrc::PllUsb => pll_usb_freq, PeriClkSrc::Rosc => rosc_freq, PeriClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // PeriClkSrc::Gpin0 => gpin0_freq, // PeriClkSrc::Gpin1 => gpin1_freq, }; @@ -595,6 +1232,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { UsbClkSrc::PllSys => pll_sys_freq, UsbClkSrc::Rosc => rosc_freq, UsbClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // UsbClkSrc::Gpin0 => gpin0_freq, // UsbClkSrc::Gpin1 => gpin1_freq, }; @@ -618,6 +1256,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { AdcClkSrc::PllSys => pll_sys_freq, AdcClkSrc::Rosc => rosc_freq, AdcClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // AdcClkSrc::Gpin0 => gpin0_freq, // AdcClkSrc::Gpin1 => gpin1_freq, }; @@ -646,6 +1285,7 @@ pub(crate) unsafe fn init(config: ClockConfig) { RtcClkSrc::PllSys => pll_sys_freq, RtcClkSrc::Rosc => rosc_freq, RtcClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out // RtcClkSrc::Gpin0 => gpin0_freq, // RtcClkSrc::Gpin1 => gpin1_freq, }; @@ -712,6 +1352,7 @@ pub fn xosc_freq() -> u32 { CLOCKS.xosc.load(Ordering::Relaxed) } +// See above re gpin handling being commented out // pub fn gpin0_freq() -> u32 { // CLOCKS.gpin0.load(Ordering::Relaxed) // } @@ -760,6 +1401,58 @@ pub fn clk_rtc_freq() -> u16 { CLOCKS.rtc.load(Ordering::Relaxed) } +/// The core voltage of the chip. +/// +/// Returns the current core voltage or an error if the voltage register +/// contains an unknown value. +pub fn core_voltage() -> Result { + #[cfg(feature = "rp2040")] + { + let vreg = pac::VREG_AND_CHIP_RESET; + let vsel = vreg.vreg().read().vsel(); + match vsel { + 0b0000 => Ok(CoreVoltage::V0_80), + 0b0110 => Ok(CoreVoltage::V0_85), + 0b0111 => Ok(CoreVoltage::V0_90), + 0b1000 => Ok(CoreVoltage::V0_95), + 0b1001 => Ok(CoreVoltage::V1_00), + 0b1010 => Ok(CoreVoltage::V1_05), + 0b1011 => Ok(CoreVoltage::V1_10), + 0b1100 => Ok(CoreVoltage::V1_15), + 0b1101 => Ok(CoreVoltage::V1_20), + 0b1110 => Ok(CoreVoltage::V1_25), + 0b1111 => Ok(CoreVoltage::V1_30), + _ => Err(ClockError::UnexpectedCoreVoltageRead), + } + } + + #[cfg(feature = "_rp235x")] + { + let vreg = pac::POWMAN; + let vsel = vreg.vreg().read().vsel(); + match vsel { + 0b00000 => Ok(CoreVoltage::V0_55), + 0b00001 => Ok(CoreVoltage::V0_60), + 0b00010 => Ok(CoreVoltage::V0_65), + 0b00011 => Ok(CoreVoltage::V0_70), + 0b00100 => Ok(CoreVoltage::V0_75), + 0b00101 => Ok(CoreVoltage::V0_80), + 0b00110 => Ok(CoreVoltage::V0_85), + 0b00111 => Ok(CoreVoltage::V0_90), + 0b01000 => Ok(CoreVoltage::V0_95), + 0b01001 => Ok(CoreVoltage::V1_00), + 0b01010 => Ok(CoreVoltage::V1_05), + 0b01011 => Ok(CoreVoltage::V1_10), + 0b01100 => Ok(CoreVoltage::V1_15), + 0b01101 => Ok(CoreVoltage::V1_20), + 0b01110 => Ok(CoreVoltage::V1_25), + 0b01111 => Ok(CoreVoltage::V1_30), + _ => Err(ClockError::UnexpectedCoreVoltageRead), + // see CoreVoltage: we do not support setting Voltages higher than 1.30V at this point + } + } +} + fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { let startup_delay = (((crystal_hz / 1000) * delay_multiplier) + 128) / 256; pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16)); @@ -770,46 +1463,100 @@ fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { while !pac::XOSC.status().read().stable() {} } +/// PLL (Phase-Locked Loop) configuration #[inline(always)] -fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { +fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> Result { + // Calculate reference frequency let ref_freq = input_freq / config.refdiv as u32; + + // Validate PLL parameters + // Feedback divider (FBDIV) must be between 16 and 320 assert!(config.fbdiv >= 16 && config.fbdiv <= 320); + + // Post divider 1 (POSTDIV1) must be between 1 and 7 assert!(config.post_div1 >= 1 && config.post_div1 <= 7); + + // Post divider 2 (POSTDIV2) must be between 1 and 7 assert!(config.post_div2 >= 1 && config.post_div2 <= 7); + + // Post divider 2 (POSTDIV2) must be less than or equal to post divider 1 (POSTDIV1) + assert!(config.post_div2 <= config.post_div1); + + // Reference divider (REFDIV) must be between 1 and 63 assert!(config.refdiv >= 1 && config.refdiv <= 63); + + // Reference frequency (REF_FREQ) must be between 5MHz and 800MHz assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); + + // Calculate VCO frequency let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); + + // VCO (Voltage Controlled Oscillator) frequency must be between 750MHz and 1800MHz assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); - // Load VCO-related dividers before starting VCO - p.cs().write(|w| w.set_refdiv(config.refdiv as _)); - p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); + // We follow the SDK's approach to PLL configuration which is: + // 1. Power down PLL + // 2. Configure the reference divider + // 3. Configure the feedback divider + // 4. Power up PLL and VCO + // 5. Wait for PLL to lock + // 6. Configure post-dividers + // 7. Enable post-divider output - // Turn on PLL - let pwr = p.pwr().write(|w| { - w.set_dsmpd(true); // "nothing is achieved by setting this low" - w.set_pd(false); - w.set_vcopd(false); - w.set_postdivpd(true); + // 1. Power down PLL before configuration + p.pwr().write(|w| { + w.set_pd(true); // Power down the PLL + w.set_vcopd(true); // Power down the VCO + w.set_postdivpd(true); // Power down the post divider + w.set_dsmpd(true); // Disable fractional mode *w }); - // Wait for PLL to lock - while !p.cs().read().lock() {} + // Short delay after powering down + cortex_m::asm::delay(10); - // Set post-dividers + // 2. Configure reference divider first + p.cs().write(|w| w.set_refdiv(config.refdiv as _)); + + // 3. Configure feedback divider + p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); + + // 4. Power up PLL and VCO, but keep post divider powered down during initial lock + p.pwr().write(|w| { + w.set_pd(false); // Power up the PLL + w.set_vcopd(false); // Power up the VCO + w.set_postdivpd(true); // Keep post divider powered down during initial lock + w.set_dsmpd(true); // Disable fractional mode (simpler configuration) + *w + }); + + // 5. Wait for PLL to lock with a timeout + let mut timeout = 1_000_000; + while !p.cs().read().lock() { + timeout -= 1; + if timeout == 0 { + // PLL failed to lock, return 0 to indicate failure + return Err(ClockError::PllLockTimedOut); + } + } + + // 6. Configure post dividers after PLL is locked p.prim().write(|w| { w.set_postdiv1(config.post_div1); w.set_postdiv2(config.post_div2); }); - // Turn on post divider - p.pwr().write(|w| { - *w = pwr; - w.set_postdivpd(false); + // 7. Enable the post divider output + p.pwr().modify(|w| { + w.set_postdivpd(false); // Power up post divider + *w }); - vco_freq / ((config.post_div1 * config.post_div2) as u32) + // Final delay to ensure everything is stable + cortex_m::asm::delay(100); + + // Calculate and return actual output frequency + Ok(vco_freq / ((config.post_div1 * config.post_div2) as u32)) } /// General purpose input clock pin. @@ -831,30 +1578,35 @@ impl_gpinpin!(PIN_22, 22, 1); /// General purpose clock input driver. pub struct Gpin<'d, T: GpinPin> { - gpin: PeripheralRef<'d, AnyPin>, + gpin: Peri<'d, AnyPin>, _phantom: PhantomData, } impl<'d, T: GpinPin> Gpin<'d, T> { /// Create new gpin driver. - pub fn new(gpin: impl Peripheral

+ 'd) -> Self { - into_ref!(gpin); - + pub fn new(gpin: Peri<'d, T>) -> Self { + #[cfg(feature = "rp2040")] gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + // On RP2350 GPIN changed from F8 toF9 + #[cfg(feature = "_rp235x")] + gpin.gpio().ctrl().write(|w| w.set_funcsel(0x09)); + + #[cfg(feature = "_rp235x")] + gpin.pad_ctrl().write(|w| { + w.set_iso(false); + }); + Gpin { - gpin: gpin.map_into(), + gpin: gpin.into(), _phantom: PhantomData, } } - - // fn map_into(self) -> Gpin<'d, AnyPin> { - // unsafe { core::mem::transmute(self) } - // } } impl<'d, T: GpinPin> Drop for Gpin<'d, T> { fn drop(&mut self) { + self.gpin.pad_ctrl().write(|_| {}); self.gpin .gpio() .ctrl() @@ -888,6 +1640,7 @@ impl_gpoutpin!(PIN_25, 3); pub enum GpoutSrc { /// Sys PLL. PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, + // See above re gpin handling being commented out // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , /// USB PLL. @@ -911,16 +1664,24 @@ pub enum GpoutSrc { /// General purpose clock output driver. pub struct Gpout<'d, T: GpoutPin> { - gpout: PeripheralRef<'d, T>, + gpout: Peri<'d, T>, } impl<'d, T: GpoutPin> Gpout<'d, T> { - /// Create new general purpose cloud output. - pub fn new(gpout: impl Peripheral

+ 'd) -> Self { - into_ref!(gpout); - + /// Create new general purpose clock output. + pub fn new(gpout: Peri<'d, T>) -> Self { + #[cfg(feature = "rp2040")] gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + // On RP2350 GPOUT changed from F8 toF9 + #[cfg(feature = "_rp235x")] + gpout.gpio().ctrl().write(|w| w.set_funcsel(0x09)); + + #[cfg(feature = "_rp235x")] + gpout.pad_ctrl().write(|w| { + w.set_iso(false); + }); + Self { gpout } } @@ -975,6 +1736,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { let base = match src { ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), + // See above re gpin handling being commented out // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), @@ -983,7 +1745,6 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), - //ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _, ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), _ => unreachable!(), }; @@ -999,6 +1760,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { fn drop(&mut self) { self.disable(); + self.gpout.pad_ctrl().write(|_| {}); self.gpout .gpio() .ctrl() @@ -1014,7 +1776,8 @@ impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { pub struct RoscRng; impl RoscRng { - fn next_u8() -> u8 { + /// Get a random u8 + pub fn next_u8() -> u8 { let random_reg = pac::ROSC.randombit(); let mut acc = 0; for _ in 0..u8::BITS { @@ -1023,25 +1786,60 @@ impl RoscRng { } acc } -} -impl rand_core::RngCore for RoscRng { - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - Ok(self.fill_bytes(dest)) + /// Get a random u32 + pub fn next_u32(&mut self) -> u32 { + rand_core_09::impls::next_u32_via_fill(self) } - fn next_u32(&mut self) -> u32 { - rand_core::impls::next_u32_via_fill(self) + /// Get a random u64 + pub fn next_u64(&mut self) -> u64 { + rand_core_09::impls::next_u64_via_fill(self) } - fn next_u64(&mut self) -> u64 { - rand_core::impls::next_u64_via_fill(self) - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { + /// Fill a slice with random bytes + pub fn fill_bytes(&mut self, dest: &mut [u8]) { dest.fill_with(Self::next_u8) } } + +impl rand_core_06::RngCore for RoscRng { + fn next_u32(&mut self) -> u32 { + self.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +impl rand_core_06::CryptoRng for RoscRng {} + +impl rand_core_09::RngCore for RoscRng { + fn next_u32(&mut self) -> u32 { + self.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill_bytes(dest); + } +} + +impl rand_core_09::CryptoRng for RoscRng {} + /// Enter the `DORMANT` sleep state. This will stop *all* internal clocks /// and can only be exited through resets, dormant-wake GPIO interrupts, /// and RTC interrupts. If RTC is clocked from an internal clock source @@ -1170,3 +1968,196 @@ pub fn dormant_sleep() { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "rp2040")] + #[test] + fn test_find_pll_params() { + #[cfg(feature = "rp2040")] + { + // Test standard 125 MHz configuration with 12 MHz crystal + let params = find_pll_params(12_000_000, 125_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + assert_eq!(params.fbdiv, 125); + + // Test USB PLL configuration for 48MHz + // The algorithm may find different valid parameters than the SDK defaults + // We'll check that it generates a valid configuration that produces 48MHz + let params = find_pll_params(12_000_000, 48_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + + // Calculate the actual output frequency + let ref_freq = 12_000_000 / params.refdiv as u32; + let vco_freq = ref_freq as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // Verify the output frequency is correct + assert_eq!(output_freq, 48_000_000); + + // Verify VCO frequency is in valid range + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + + // Test overclocked configuration for 200 MHz + let params = find_pll_params(12_000_000, 200_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + let vco_freq = 12_000_000 as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + assert_eq!(output_freq, 200_000_000); + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); // VCO in valid range + + // Test non-standard crystal with 16 MHz + let params = find_pll_params(16_000_000, 125_000_000).unwrap(); + let vco_freq = (16_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // Test non-standard crystal with 15 MHz + let params = find_pll_params(15_000_000, 125_000_000).unwrap(); + let vco_freq = (15_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // With a 15 MHz crystal, we might not get exactly 125 MHz + // Check that it's close enough (within 0.2% margin) + let freq_diff = if output_freq > 125_000_000 { + output_freq - 125_000_000 + } else { + 125_000_000 - output_freq + }; + let error_percentage = (freq_diff as f64 / 125_000_000.0) * 100.0; + assert!( + error_percentage < 0.2, + "Output frequency {} is not close enough to target 125 MHz. Error: {:.2}%", + output_freq, + error_percentage + ); + + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + } + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_pll_config_validation() { + // Test PLL configuration validation logic + let valid_config = PllConfig { + refdiv: 1, + fbdiv: 125, + post_div1: 6, + post_div2: 2, + }; + + // Valid configuration should pass validation + assert!(valid_config.is_valid(12_000_000)); + + // Test fbdiv constraints + let mut invalid_config = valid_config; + invalid_config.fbdiv = 15; // Below minimum of 16 + assert!(!invalid_config.is_valid(12_000_000)); + + invalid_config.fbdiv = 321; // Above maximum of 320 + assert!(!invalid_config.is_valid(12_000_000)); + + // Test post_div constraints + invalid_config = valid_config; + invalid_config.post_div1 = 0; // Below minimum of 1 + assert!(!invalid_config.is_valid(12_000_000)); + + invalid_config = valid_config; + invalid_config.post_div1 = 8; // Above maximum of 7 + assert!(!invalid_config.is_valid(12_000_000)); + + // Test post_div2 must be <= post_div1 + invalid_config = valid_config; + invalid_config.post_div2 = 7; + invalid_config.post_div1 = 3; + assert!(!invalid_config.is_valid(12_000_000)); + + // Test reference frequency constraints + invalid_config = valid_config; + assert!(!invalid_config.is_valid(4_000_000)); // Below minimum of 5 MHz + assert!(!invalid_config.is_valid(900_000_000)); // Above maximum of 800 MHz + + // Test VCO frequency constraints + invalid_config = valid_config; + invalid_config.fbdiv = 16; + assert!(!invalid_config.is_valid(12_000_000)); // VCO too low: 12MHz * 16 = 192MHz + + // Test VCO frequency constraints - too high + invalid_config = valid_config; + invalid_config.fbdiv = 200; + invalid_config.refdiv = 1; + // This should be INVALID: 12MHz * 200 = 2400MHz exceeds max VCO of 1800MHz + assert!(!invalid_config.is_valid(12_000_000)); + + // Test a valid high VCO configuration + invalid_config.fbdiv = 150; // 12MHz * 150 = 1800MHz, exactly at the limit + assert!(invalid_config.is_valid(12_000_000)); + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_manual_pll_helper() { + { + // Test the new manual_pll helper method + let config = ClockConfig::manual_pll( + 12_000_000, + PllConfig { + refdiv: 1, + fbdiv: 100, + post_div1: 3, + post_div2: 2, + }, + CoreVoltage::V1_15, + ); + + // Check voltage scale was set correctly + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Check PLL config was set correctly + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().refdiv, 1); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().fbdiv, 100); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div1, 3); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div2, 2); + + // Check we get the expected frequency + assert_eq!( + config + .xosc + .as_ref() + .unwrap() + .sys_pll + .as_ref() + .unwrap() + .output_frequency(12_000_000), + 200_000_000 + ); + } + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_auto_voltage_scaling() { + { + // Test automatic voltage scaling based on frequency + // Under 133 MHz should use default voltage (V1_10) + let config = ClockConfig::system_freq(125_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_10); + + // 133-200 MHz should use V1_15 + let config = ClockConfig::system_freq(150_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + let config = ClockConfig::system_freq(200_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Above 200 MHz should use V1_15 + let config = ClockConfig::system_freq(250_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Below 125 MHz should use V1_10 + let config = ClockConfig::system_freq(100_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_10); + } + } +} diff --git a/embassy-rp/src/critical_section_impl.rs b/embassy-rp/src/critical_section_impl.rs index d233e6fab..2e4e8f716 100644 --- a/embassy-rp/src/critical_section_impl.rs +++ b/embassy-rp/src/critical_section_impl.rs @@ -1,6 +1,7 @@ use core::sync::atomic::{AtomicU8, Ordering}; use crate::pac; +use crate::spinlock::Spinlock; struct RpSpinlockCs; critical_section::set_impl!(RpSpinlockCs); @@ -92,46 +93,4 @@ impl RpSpinlockCs { } } -pub struct Spinlock(core::marker::PhantomData<()>) -where - Spinlock: SpinlockValid; - -impl Spinlock -where - Spinlock: SpinlockValid, -{ - /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is - /// already in use somewhere else. - pub fn try_claim() -> Option { - let lock = pac::SIO.spinlock(N).read(); - if lock > 0 { - Some(Self(core::marker::PhantomData)) - } else { - None - } - } - - /// Clear a locked spin-lock. - /// - /// # Safety - /// - /// Only call this function if you hold the spin-lock. - pub unsafe fn release() { - // Write (any value): release the lock - pac::SIO.spinlock(N).write_value(1); - } -} - -impl Drop for Spinlock -where - Spinlock: SpinlockValid, -{ - fn drop(&mut self) { - // This is safe because we own the object, and hence hold the lock. - unsafe { Self::release() } - } -} - pub(crate) type Spinlock31 = Spinlock<31>; -pub trait SpinlockValid {} -impl SpinlockValid for Spinlock<31> {} diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index 34abe3e2d..d31d1e159 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs @@ -4,7 +4,7 @@ use core::pin::Pin; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::{Context, Poll}; -use embassy_hal_internal::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use pac::dma::vals::DataSize; @@ -42,7 +42,7 @@ pub(crate) unsafe fn init() { /// /// SAFETY: Slice must point to a valid location reachable by DMA. pub unsafe fn read<'a, C: Channel, W: Word>( - ch: impl Peripheral

+ 'a, + ch: Peri<'a, C>, from: *const W, to: *mut [W], dreq: vals::TreqSel, @@ -63,7 +63,7 @@ pub unsafe fn read<'a, C: Channel, W: Word>( /// /// SAFETY: Slice must point to a valid location reachable by DMA. pub unsafe fn write<'a, C: Channel, W: Word>( - ch: impl Peripheral

+ 'a, + ch: Peri<'a, C>, from: *const [W], to: *mut W, dreq: vals::TreqSel, @@ -87,7 +87,7 @@ static mut DUMMY: u32 = 0; /// /// SAFETY: Slice must point to a valid location reachable by DMA. pub unsafe fn write_repeated<'a, C: Channel, W: Word>( - ch: impl Peripheral

+ 'a, + ch: Peri<'a, C>, to: *mut W, len: usize, dreq: vals::TreqSel, @@ -107,11 +107,7 @@ pub unsafe fn write_repeated<'a, C: Channel, W: Word>( /// DMA copy between slices. /// /// SAFETY: Slices must point to locations reachable by DMA. -pub unsafe fn copy<'a, C: Channel, W: Word>( - ch: impl Peripheral

+ 'a, - from: &[W], - to: &mut [W], -) -> Transfer<'a, C> { +pub unsafe fn copy<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: &[W], to: &mut [W]) -> Transfer<'a, C> { let from_len = from.len(); let to_len = to.len(); assert_eq!(from_len, to_len); @@ -128,7 +124,7 @@ pub unsafe fn copy<'a, C: Channel, W: Word>( } fn copy_inner<'a, C: Channel>( - ch: impl Peripheral

+ 'a, + ch: Peri<'a, C>, from: *const u32, to: *mut u32, len: usize, @@ -137,8 +133,6 @@ fn copy_inner<'a, C: Channel>( incr_write: bool, dreq: vals::TreqSel, ) -> Transfer<'a, C> { - into_ref!(ch); - let p = ch.regs(); p.read_addr().write_value(from as u32); @@ -171,13 +165,11 @@ fn copy_inner<'a, C: Channel>( /// DMA transfer driver. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a, C: Channel> { - channel: PeripheralRef<'a, C>, + channel: Peri<'a, C>, } impl<'a, C: Channel> Transfer<'a, C> { - pub(crate) fn new(channel: impl Peripheral

+ 'a) -> Self { - into_ref!(channel); - + pub(crate) fn new(channel: Peri<'a, C>) -> Self { Self { channel } } } @@ -212,15 +204,14 @@ impl<'a, C: Channel> Future for Transfer<'a, C> { pub(crate) const CHANNEL_COUNT: usize = 12; #[cfg(feature = "_rp235x")] pub(crate) const CHANNEL_COUNT: usize = 16; -const NEW_AW: AtomicWaker = AtomicWaker::new(); -static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; trait SealedChannel {} trait SealedWord {} /// DMA channel interface. #[allow(private_bounds)] -pub trait Channel: Peripheral

+ SealedChannel + Into + Sized + 'static { +pub trait Channel: PeripheralType + SealedChannel + Into + Sized + 'static { /// Channel number. fn number(&self) -> u8; @@ -228,11 +219,6 @@ pub trait Channel: Peripheral

+ SealedChannel + Into + Siz fn regs(&self) -> pac::dma::Channel { pac::DMA.ch(self.number() as _) } - - /// Convert into type-erased [AnyChannel]. - fn degrade(self) -> AnyChannel { - AnyChannel { number: self.number() } - } } /// DMA word. @@ -288,7 +274,7 @@ macro_rules! channel { impl From for crate::dma::AnyChannel { fn from(val: peripherals::$name) -> Self { - crate::dma::Channel::degrade(val) + Self { number: val.number() } } } }; diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index a68493932..ef1cd9212 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs @@ -4,7 +4,7 @@ use core::marker::PhantomData; use core::pin::Pin; use core::task::{Context, Poll}; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embedded_storage::nor_flash::{ check_erase, check_read, check_write, ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, @@ -17,9 +17,13 @@ use crate::peripherals::FLASH; /// Flash base address. pub const FLASH_BASE: *const u32 = 0x10000000 as _; +/// Address for xip setup function set up by the 235x bootrom. +#[cfg(feature = "_rp235x")] +pub const BOOTRAM_BASE: *const u32 = 0x400e0000 as _; + /// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead. // TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance. -pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram"); +pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x"); // **NOTE**: // @@ -97,7 +101,10 @@ impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, ' // Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in // flight that might effect an address written to start a new transfer. This stalls // until after any transfer is complete, so the address will not change anymore. + #[cfg(feature = "rp2040")] const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _; unsafe { core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE); } @@ -107,7 +114,7 @@ impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, ' /// Flash driver. pub struct Flash<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> { - dma: Option>, + dma: Option>, phantom: PhantomData<(&'d mut T, M)>, } @@ -225,12 +232,14 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI } /// Read SPI flash unique ID + #[cfg(feature = "rp2040")] pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? }; Ok(()) } /// Read SPI flash JEDEC ID + #[cfg(feature = "rp2040")] pub fn blocking_jedec_id(&mut self) -> Result { let mut jedec = None; unsafe { @@ -244,7 +253,7 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Blocking, FLASH_SIZE> { /// Create a new flash driver in blocking mode. - pub fn new_blocking(_flash: impl Peripheral

+ 'd) -> Self { + pub fn new_blocking(_flash: Peri<'d, T>) -> Self { Self { dma: None, phantom: PhantomData, @@ -254,10 +263,9 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Blocking, FLASH_SIZE impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> { /// Create a new flash driver in async mode. - pub fn new(_flash: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd) -> Self { - into_ref!(dma); + pub fn new(_flash: Peri<'d, T>, dma: Peri<'d, impl Channel>) -> Self { Self { - dma: Some(dma.map_into()), + dma: Some(dma.into()), phantom: PhantomData, } } @@ -301,10 +309,13 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> { // Use the XIP AUX bus port, rather than the FIFO register access (e.x. // pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on // general XIP access. + #[cfg(feature = "rp2040")] const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _; let transfer = unsafe { crate::dma::read( - self.dma.as_mut().unwrap(), + self.dma.as_mut().unwrap().reborrow(), XIP_AUX_BASE, data, pac::dma::vals::TreqSel::XIP_STREAM, @@ -512,7 +523,10 @@ mod ram_helpers { pub unsafe fn flash_range_erase(addr: u32, len: u32) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTRAM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); flash_function_pointers_with_boot2(true, false, &boot2) } else { flash_function_pointers(true, false) @@ -542,7 +556,10 @@ mod ram_helpers { pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTRAM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256); flash_function_pointers_with_boot2(true, true, &boot2) } else { flash_function_pointers(true, true) @@ -577,7 +594,10 @@ mod ram_helpers { pub unsafe fn flash_range_program(addr: u32, data: &[u8]) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTRAM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); flash_function_pointers_with_boot2(false, true, &boot2) } else { flash_function_pointers(false, true) @@ -606,15 +626,6 @@ mod ram_helpers { #[link_section = ".data.ram_func"] #[cfg(feature = "rp2040")] unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { - /* - Should be equivalent to: - rom_data::connect_internal_flash(); - rom_data::flash_exit_xip(); - rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected - rom_data::flash_range_program(addr, data as *const _, len); // if selected - rom_data::flash_flush_cache(); - rom_data::flash_enter_cmd_xip(); - */ #[cfg(target_arch = "arm")] core::arch::asm!( "mov r8, r0", @@ -667,11 +678,30 @@ mod ram_helpers { ); } + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// Length of data must be a multiple of 4096 + /// addr must be aligned to 4096 #[inline(never)] #[link_section = ".data.ram_func"] #[cfg(feature = "_rp235x")] - unsafe fn write_flash_inner(_addr: u32, _len: u32, _data: Option<&[u8]>, _ptrs: *const FlashFunctionPointers) { - todo!(); + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()); + ((*ptrs).connect_internal_flash)(); + ((*ptrs).flash_exit_xip)(); + if (*ptrs).flash_range_erase.is_some() { + ((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0); + } + if (*ptrs).flash_range_program.is_some() { + ((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize); + } + ((*ptrs).flash_flush_cache)(); + ((*ptrs).flash_enter_cmd_xip)(); } #[repr(C)] @@ -707,6 +737,7 @@ mod ram_helpers { /// - DMA must not access flash memory /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] pub unsafe fn flash_unique_id(out: &mut [u8]) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { @@ -715,6 +746,7 @@ mod ram_helpers { } else { flash_function_pointers(false, false) }; + // 4B - read unique ID let cmd = [0x4B]; read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers); @@ -735,6 +767,7 @@ mod ram_helpers { /// - DMA must not access flash memory /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] pub unsafe fn flash_jedec_id() -> u32 { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { @@ -743,6 +776,7 @@ mod ram_helpers { } else { flash_function_pointers(false, false) }; + let mut id = [0u8; 4]; // 9F - read JEDEC ID let cmd = [0x9F]; @@ -750,6 +784,7 @@ mod ram_helpers { u32::from_be_bytes(id) } + #[cfg(feature = "rp2040")] unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) { read_flash_inner( FlashCommand { @@ -890,13 +925,6 @@ mod ram_helpers { clobber_abi("C"), ); } - - #[inline(never)] - #[link_section = ".data.ram_func"] - #[cfg(feature = "_rp235x")] - unsafe fn read_flash_inner(_cmd: FlashCommand, _ptrs: *const FlashFunctionPointers) { - todo!(); - } } /// Make sure to uphold the contract points with rp2040-flash. @@ -936,7 +964,7 @@ trait SealedMode {} /// Flash instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance {} +pub trait Instance: SealedInstance + PeripheralType {} /// Flash mode. #[allow(private_bounds)] pub trait Mode: SealedMode {} diff --git a/embassy-rp/src/float/div.rs b/embassy-rp/src/float/div.rs index aff0dcb07..87d1e38e5 100644 --- a/embassy-rp/src/float/div.rs +++ b/embassy-rp/src/float/div.rs @@ -40,7 +40,7 @@ where // using a wrong divisor, but we'll restore the divisor and result ourselves correctly. // This sets DIRTY, so any interruptor will save the state. sio.div().udividend().write_value(dividend); - // If we are interrupted here, the the interruptor may start the calculation using + // If we are interrupted here, the interruptor may start the calculation using // incorrectly signed inputs, but we'll restore the result ourselves. // This sets DIRTY, so any interruptor will save the state. sio.div().udivisor().write_value(divisor); diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index d0bb7e574..9b5faac15 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -5,26 +5,24 @@ use core::future::Future; use core::pin::Pin as FuturePin; use core::task::{Context, Poll}; -use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use crate::interrupt::InterruptExt; use crate::pac::common::{Reg, RW}; use crate::pac::SIO; -use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; - -const NEW_AW: AtomicWaker = AtomicWaker::new(); +use crate::{interrupt, pac, peripherals, RegExt}; #[cfg(any(feature = "rp2040", feature = "rp235xa"))] -const BANK0_PIN_COUNT: usize = 30; +pub(crate) const BANK0_PIN_COUNT: usize = 30; #[cfg(feature = "rp235xb")] -const BANK0_PIN_COUNT: usize = 48; +pub(crate) const BANK0_PIN_COUNT: usize = 48; -static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [NEW_AW; BANK0_PIN_COUNT]; +static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [const { AtomicWaker::new() }; BANK0_PIN_COUNT]; #[cfg(feature = "qspi-as-gpio")] const QSPI_PIN_COUNT: usize = 6; #[cfg(feature = "qspi-as-gpio")] -static QSPI_WAKERS: [AtomicWaker; QSPI_PIN_COUNT] = [NEW_AW; QSPI_PIN_COUNT]; +static QSPI_WAKERS: [AtomicWaker; QSPI_PIN_COUNT] = [const { AtomicWaker::new() }; QSPI_PIN_COUNT]; /// Represents a digital input or output level. #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -117,7 +115,7 @@ pub struct Input<'d> { impl<'d> Input<'d> { /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. #[inline] - pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(); pin.set_pull(pull); @@ -148,6 +146,12 @@ impl<'d> Input<'d> { self.pin.get_level() } + /// Configure the input logic inversion of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + self.pin.set_input_inversion(invert) + } + /// Wait until the pin is high. If it is already high, return immediately. #[inline] pub async fn wait_for_high(&mut self) { @@ -268,11 +272,11 @@ fn IO_IRQ_QSPI() { #[must_use = "futures do nothing unless you `.await` or poll them"] struct InputFuture<'d> { - pin: PeripheralRef<'d, AnyPin>, + pin: Peri<'d, AnyPin>, } impl<'d> InputFuture<'d> { - fn new(pin: PeripheralRef<'d, AnyPin>, level: InterruptTrigger) -> Self { + fn new(pin: Peri<'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 @@ -361,7 +365,7 @@ pub struct Output<'d> { impl<'d> Output<'d> { /// Create GPIO output driver for a [Pin] with the provided [Level]. #[inline] - pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -384,6 +388,12 @@ impl<'d> Output<'d> { self.pin.set_slew_rate(slew_rate) } + /// Configure the output logic inversion of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + self.pin.set_output_inversion(invert) + } + /// Set the output as high. #[inline] pub fn set_high(&mut self) { @@ -442,7 +452,7 @@ pub struct OutputOpenDrain<'d> { 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

+ 'd, initial_output: Level) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { let mut pin = Flex::new(pin); pin.set_low(); match initial_output { @@ -452,6 +462,16 @@ impl<'d> OutputOpenDrain<'d> { Self { pin } } + /// Set the pin's pull-up. + #[inline] + pub fn set_pullup(&mut self, enable: bool) { + if enable { + self.pin.set_pull(Pull::Up); + } else { + self.pin.set_pull(Pull::None); + } + } + /// Set the pin's drive strength. #[inline] pub fn set_drive_strength(&mut self, strength: Drive) { @@ -573,7 +593,7 @@ impl<'d> OutputOpenDrain<'d> { /// 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> { - pin: PeripheralRef<'d, AnyPin>, + pin: Peri<'d, AnyPin>, } impl<'d> Flex<'d> { @@ -582,9 +602,7 @@ impl<'d> Flex<'d> { /// 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

+ 'd) -> Self { - into_ref!(pin); - + pub fn new(pin: Peri<'d, impl Pin>) -> Self { pin.pad_ctrl().write(|w| { #[cfg(feature = "_rp235x")] w.set_iso(false); @@ -598,12 +616,12 @@ impl<'d> Flex<'d> { w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIOB_PROC_0 as _); }); - Self { pin: pin.map_into() } + Self { pin: pin.into() } } #[inline] fn bit(&self) -> u32 { - 1 << self.pin.pin() + 1 << (self.pin.pin() % 32) } /// Set the pin's pull. @@ -626,10 +644,10 @@ impl<'d> Flex<'d> { pub fn set_drive_strength(&mut self, strength: Drive) { self.pin.pad_ctrl().modify(|w| { w.set_drive(match strength { - Drive::_2mA => pac::pads::vals::Drive::_2MA, - Drive::_4mA => pac::pads::vals::Drive::_4MA, - Drive::_8mA => pac::pads::vals::Drive::_8MA, - Drive::_12mA => pac::pads::vals::Drive::_12MA, + Drive::_2mA => pac::pads::vals::Drive::_2M_A, + Drive::_4mA => pac::pads::vals::Drive::_4M_A, + Drive::_8mA => pac::pads::vals::Drive::_8M_A, + Drive::_12mA => pac::pads::vals::Drive::_12M_A, }); }); } @@ -679,6 +697,30 @@ impl<'d> Flex<'d> { self.pin.sio_oe().value_xor().write_value(self.bit()) } + /// Configure the input logic inversion of this pin. + #[inline] + pub fn set_input_inversion(&mut self, invert: bool) { + self.pin.gpio().ctrl().modify(|w| { + w.set_inover(if invert { + pac::io::vals::Inover::INVERT + } else { + pac::io::vals::Inover::NORMAL + }) + }); + } + + /// Configure the output logic inversion of this pin. + #[inline] + pub fn set_output_inversion(&mut self, invert: bool) { + self.pin.gpio().ctrl().modify(|w| { + w.set_outover(if invert { + pac::io::vals::Outover::INVERT + } else { + pac::io::vals::Outover::NORMAL + }) + }); + } + /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { @@ -809,6 +851,8 @@ impl<'d> Drop for Flex<'d> { self.pin.pad_ctrl().write(|_| {}); self.pin.gpio().ctrl().write(|w| { w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _); + w.set_inover(pac::io::vals::Inover::NORMAL); + w.set_outover(pac::io::vals::Outover::NORMAL); }); self.pin.io().int_dormant_wake().inte(idx / 8).write_clear(|w| { w.set_edge_high(idx % 8, true); @@ -821,7 +865,7 @@ impl<'d> Drop for Flex<'d> { /// Dormant wake driver. pub struct DormantWake<'w> { - pin: PeripheralRef<'w, AnyPin>, + pin: Peri<'w, AnyPin>, cfg: DormantWakeConfig, } @@ -846,12 +890,12 @@ pub(crate) trait SealedPin: Sized { #[inline] fn _pin(&self) -> u8 { - self.pin_bank() & 0x1f + self.pin_bank() & 0x7f } #[inline] fn _bank(&self) -> Bank { - match self.pin_bank() >> 5 { + match self.pin_bank() >> 7 { #[cfg(feature = "qspi-as-gpio")] 1 => Bank::Qspi, _ => Bank::Bank0, @@ -880,15 +924,27 @@ pub(crate) trait SealedPin: Sized { } fn sio_out(&self) -> pac::sio::Gpio { - SIO.gpio_out(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_out(self._bank() as _) + } else { + SIO.gpio_out((self._pin() / 32) as _) + } } fn sio_oe(&self) -> pac::sio::Gpio { - SIO.gpio_oe(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_oe(self._bank() as _) + } else { + SIO.gpio_oe((self._pin() / 32) as _) + } } fn sio_in(&self) -> Reg { - SIO.gpio_in(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_in(self._bank() as _) + } else { + SIO.gpio_in((self._pin() / 32) as _) + } } fn int_proc(&self) -> pac::io::Int { @@ -899,14 +955,7 @@ pub(crate) trait SealedPin: Sized { /// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an [AnyPin]. #[allow(private_bounds)] -pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { - /// Degrade to a generic pin struct - fn degrade(self) -> AnyPin { - AnyPin { - pin_bank: self.pin_bank(), - } - } - +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { /// Returns the pin number within a bank #[inline] fn pin(&self) -> u8 { @@ -921,6 +970,8 @@ pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static } /// Type-erased GPIO pin +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct AnyPin { pin_bank: u8, } @@ -931,8 +982,8 @@ impl AnyPin { /// # 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 } + pub unsafe fn steal(pin_bank: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_bank }) } } @@ -953,13 +1004,15 @@ macro_rules! impl_pin { impl SealedPin for peripherals::$name { #[inline] fn pin_bank(&self) -> u8 { - ($bank as u8) * 32 + $pin_num + ($bank as u8) * 128 + $pin_num } } impl From for crate::gpio::AnyPin { fn from(val: peripherals::$name) -> Self { - crate::gpio::Pin::degrade(val) + Self { + pin_bank: val.pin_bank(), + } } } }; diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs index 32778215f..a983b7bc3 100644 --- a/embassy-rp/src/i2c.rs +++ b/embassy-rp/src/i2c.rs @@ -1,15 +1,17 @@ +#![cfg_attr(feature = "defmt", allow(deprecated))] // Suppress warnings for defmt::Format using Error::AddressReserved + //! I2C driver. use core::future; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use pac::i2c; use crate::gpio::AnyPin; use crate::interrupt::typelevel::{Binding, Interrupt}; -use crate::{interrupt, pac, peripherals, Peripheral}; +use crate::{interrupt, pac, peripherals}; /// I2C error abort reason #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -40,6 +42,7 @@ pub enum Error { /// Target i2c address is out of range AddressOutOfRange(u16), /// Target i2c address is reserved + #[deprecated = "embassy_rp no longer prevents accesses to reserved addresses."] AddressReserved(u16), } @@ -80,28 +83,25 @@ pub struct I2c<'d, T: Instance, M: Mode> { impl<'d, T: Instance> I2c<'d, T, Blocking> { /// Create a new driver instance in blocking mode. pub fn new_blocking( - peri: impl Peripheral

+ 'd, - scl: impl Peripheral

> + 'd, - sda: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, config: Config, ) -> Self { - into_ref!(scl, sda); - Self::new_inner(peri, scl.map_into(), sda.map_into(), config) + Self::new_inner(peri, scl.into(), sda.into(), config) } } impl<'d, T: Instance> I2c<'d, T, Async> { /// Create a new driver instance in async mode. pub fn new_async( - peri: impl Peripheral

+ 'd, - scl: impl Peripheral

> + 'd, - sda: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, _irq: impl Binding>, config: Config, ) -> Self { - into_ref!(scl, sda); - - let i2c = Self::new_inner(peri, scl.map_into(), sda.map_into(), config); + let i2c = Self::new_inner(peri, scl.into(), sda.into(), config); let r = T::regs(); @@ -114,17 +114,19 @@ impl<'d, T: Instance> I2c<'d, T, Async> { } /// Calls `f` to check if we are ready or not. - /// If not, `g` is called once the waker is set (to eg enable the required interrupts). + /// If not, `g` is called once(to eg enable the required interrupts). + /// The waker will always be registered prior to calling `f`. async fn wait_on(&mut self, mut f: F, mut g: G) -> U where F: FnMut(&mut Self) -> Poll, G: FnMut(&mut Self), { future::poll_fn(|cx| { + // Register prior to checking the condition + T::waker().register(cx.waker()); let r = f(self); if r.is_pending() { - T::waker().register(cx.waker()); g(self); } r @@ -375,14 +377,7 @@ where } impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { - fn new_inner( - _peri: impl Peripheral

+ 'd, - scl: PeripheralRef<'d, AnyPin>, - sda: PeripheralRef<'d, AnyPin>, - config: Config, - ) -> Self { - into_ref!(_peri); - + fn new_inner(_peri: Peri<'d, T>, scl: Peri<'d, AnyPin>, sda: Peri<'d, AnyPin>, config: Config) -> Self { let reset = T::reset(); crate::reset::reset(reset); crate::reset::unreset_wait(reset); @@ -470,10 +465,6 @@ impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { return Err(Error::AddressOutOfRange(addr)); } - if i2c_reserved_addr(addr) { - return Err(Error::AddressReserved(addr)); - } - let p = T::regs(); p.ic_enable().write(|w| w.set_enable(false)); p.ic_tar().write(|w| w.set_ic_tar(addr)); @@ -680,6 +671,7 @@ impl embedded_hal_1::i2c::Error for Error { Self::InvalidReadBufferLength => embedded_hal_1::i2c::ErrorKind::Other, Self::InvalidWriteBufferLength => embedded_hal_1::i2c::ErrorKind::Other, Self::AddressOutOfRange(_) => embedded_hal_1::i2c::ErrorKind::Other, + #[allow(deprecated)] Self::AddressReserved(_) => embedded_hal_1::i2c::ErrorKind::Other, } } @@ -775,11 +767,6 @@ impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> } } -/// Check if address is reserved. -pub fn i2c_reserved_addr(addr: u16) -> bool { - ((addr & 0x78) == 0 || (addr & 0x78) == 0x78) && addr != 0 -} - pub(crate) trait SealedInstance { fn regs() -> crate::pac::i2c::I2c; fn reset() -> crate::pac::resets::regs::Peripherals; @@ -809,7 +796,7 @@ impl_mode!(Async); /// I2C instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance { +pub trait Instance: SealedInstance + PeripheralType { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-rp/src/i2c_slave.rs b/embassy-rp/src/i2c_slave.rs index c46a55d2e..7bc14511d 100644 --- a/embassy-rp/src/i2c_slave.rs +++ b/embassy-rp/src/i2c_slave.rs @@ -3,14 +3,11 @@ use core::future; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::into_ref; use pac::i2c; -use crate::i2c::{ - i2c_reserved_addr, set_up_i2c_pin, AbortReason, Instance, InterruptHandler, SclPin, SdaPin, FIFO_SIZE, -}; +use crate::i2c::{set_up_i2c_pin, AbortReason, Instance, InterruptHandler, SclPin, SdaPin, FIFO_SIZE}; use crate::interrupt::typelevel::{Binding, Interrupt}; -use crate::{pac, Peripheral}; +use crate::{pac, Peri}; /// I2C error #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -89,15 +86,12 @@ pub struct I2cSlave<'d, T: Instance> { impl<'d, T: Instance> I2cSlave<'d, T> { /// Create a new instance. pub fn new( - _peri: impl Peripheral

+ 'd, - scl: impl Peripheral

> + 'd, - sda: impl Peripheral

> + 'd, + _peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, _irq: impl Binding>, config: Config, ) -> Self { - into_ref!(_peri, scl, sda); - - assert!(!i2c_reserved_addr(config.addr)); assert!(config.addr != 0); // Configure SCL & SDA pins @@ -165,7 +159,8 @@ impl<'d, T: Instance> I2cSlave<'d, T> { } /// Calls `f` to check if we are ready or not. - /// If not, `g` is called once the waker is set (to eg enable the required interrupts). + /// If not, `g` is called once(to eg enable the required interrupts). + /// The waker will always be registered prior to calling `f`. #[inline(always)] async fn wait_on(&mut self, mut f: F, mut g: G) -> U where @@ -173,10 +168,11 @@ impl<'d, T: Instance> I2cSlave<'d, T> { G: FnMut(&mut Self), { future::poll_fn(|cx| { + // Register prior to checking the condition + T::waker().register(cx.waker()); let r = f(self); if r.is_pending() { - T::waker().register(cx.waker()); g(self); } diff --git a/embassy-rp/src/intrinsics.rs b/embassy-rp/src/intrinsics.rs index 5b9c127ba..69d5d92de 100644 --- a/embassy-rp/src/intrinsics.rs +++ b/embassy-rp/src/intrinsics.rs @@ -335,10 +335,9 @@ core::arch::global_asm!( // result ourselves correctly. This sets DIRTY, so any interruptor will // save the state. "str r3, [r2, #0x060]", // DIV_UDIVIDEND - // If we are interrupted here, the the interruptor may start the - // calculation using incorrectly signed inputs, but we'll restore the - // result ourselves. This sets DIRTY, so any interruptor will save the - // state. + // If we are interrupted here, the interruptor may start the calculation + // using incorrectly signed inputs, but we'll restore the result ourselves. + // This sets DIRTY, so any interruptor will save the state. "str r4, [r2, #0x064]", // DIV_UDIVISOR // If we are interrupted here, the interruptor will have restored // everything but the quotient may be wrongly signed. If the calculation diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 1fc397107..f3c5a35bb 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -9,15 +9,17 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +#[cfg(feature = "binary-info")] +pub use rp_binary_info as binary_info; + #[cfg(feature = "critical-section-impl")] mod critical_section_impl; +#[cfg(feature = "rp2040")] mod intrinsics; pub mod adc; #[cfg(feature = "_rp235x")] -pub mod binary_info; -#[cfg(feature = "_rp235x")] pub mod block; #[cfg(feature = "rp2040")] pub mod bootsel; @@ -30,14 +32,21 @@ pub mod gpio; pub mod i2c; pub mod i2c_slave; pub mod multicore; +#[cfg(feature = "_rp235x")] +pub mod otp; +pub mod pio_programs; pub mod pwm; mod reset; pub mod rom_data; #[cfg(feature = "rp2040")] pub mod rtc; pub mod spi; +mod spinlock; +pub mod spinlock_mutex; #[cfg(feature = "time-driver")] pub mod time_driver; +#[cfg(feature = "_rp235x")] +pub mod trng; pub mod uart; pub mod usb; pub mod watchdog; @@ -47,7 +56,7 @@ pub mod pio; pub(crate) mod relocate; // Reexports -pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use embassy_hal_internal::{Peri, PeripheralType}; #[cfg(feature = "unstable-pac")] pub use rp_pac as pac; #[cfg(not(feature = "unstable-pac"))] @@ -151,32 +160,54 @@ embassy_hal_internal::interrupt_mod!( /// ```rust,ignore /// use embassy_rp::{bind_interrupts, usb, peripherals}; /// -/// bind_interrupts!(struct Irqs { -/// USBCTRL_IRQ => usb::InterruptHandler; -/// }); +/// bind_interrupts!( +/// /// Binds the USB Interrupts. +/// struct Irqs { +/// USBCTRL_IRQ => usb::InterruptHandler; +/// } +/// ); /// ``` /// // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { - #[derive(Copy, Clone)] - $vis struct $name; + ($(#[$attr:meta])* $vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; $( #[allow(non_snake_case)] #[no_mangle] + $(#[cfg($cond_irq)])? unsafe extern "C" fn $irq() { $( + $(#[cfg($cond_handler)])? <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* } - $( - unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} - )* + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); )* }; + (@inner $($t:tt)*) => { + $($t)* + } } #[cfg(feature = "rp2040")] @@ -398,9 +429,11 @@ embassy_hal_internal::peripherals! { WATCHDOG, BOOTSEL, + + TRNG } -#[cfg(not(feature = "boot2-none"))] +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] macro_rules! select_bootloader { ( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => { $( @@ -417,7 +450,7 @@ macro_rules! select_bootloader { } } -#[cfg(not(feature = "boot2-none"))] +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] select_bootloader! { "boot2-at25sf128a" => BOOT_LOADER_AT25SF128A, "boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS, @@ -429,6 +462,30 @@ select_bootloader! { default => BOOT_LOADER_W25Q080 } +#[cfg(all(not(feature = "imagedef-none"), feature = "_rp235x"))] +macro_rules! select_imagedef { + ( $( $feature:literal => $imagedef:ident, )+ default => $default:ident ) => { + $( + #[cfg(feature = $feature)] + #[link_section = ".start_block"] + #[used] + static IMAGE_DEF: crate::block::ImageDef = crate::block::ImageDef::$imagedef(); + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[link_section = ".start_block"] + #[used] + static IMAGE_DEF: crate::block::ImageDef = crate::block::ImageDef::$default(); + } +} + +#[cfg(all(not(feature = "imagedef-none"), feature = "_rp235x"))] +select_imagedef! { + "imagedef-secure-exe" => secure_exe, + "imagedef-nonsecure-exe" => non_secure_exe, + default => secure_exe +} + /// Installs a stack guard for the CORE0 stack in MPU region 0. /// Will fail if the MPU is already configured. This function requires /// a `_stack_end` symbol to be defined by the linker script, and expects @@ -471,7 +528,7 @@ pub fn install_core0_stack_guard() -> Result<(), ()> { #[cfg(all(feature = "rp2040", not(feature = "_test")))] #[inline(always)] -fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { let core = unsafe { cortex_m::Peripherals::steal() }; // Fail if MPU is already configured @@ -499,7 +556,7 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { #[cfg(all(feature = "_rp235x", not(feature = "_test")))] #[inline(always)] -fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { let core = unsafe { cortex_m::Peripherals::steal() }; // Fail if MPU is already configured @@ -519,7 +576,7 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { // so the compile fails when we try to use ARMv8 peripherals. #[cfg(feature = "_test")] #[inline(always)] -fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { +unsafe fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { Ok(()) } @@ -571,7 +628,7 @@ pub fn init(config: config::Config) -> Peripherals { peripherals } -#[cfg(all(feature = "rt", feature = "rp2040"))] +#[cfg(feature = "rt")] #[cortex_m_rt::pre_init] unsafe fn pre_init() { // SIO does not get reset when core0 is reset with either `scb::sys_reset()` or with SWD. @@ -602,17 +659,52 @@ unsafe fn pre_init() { // // The PSM order is SIO -> PROC0 -> PROC1. // So, we have to force-on PROC0 to prevent it from getting reset when resetting SIO. - pac::PSM.frce_on().write_and_wait(|w| { - w.set_proc0(true); - }); - // Then reset SIO and PROC1. - pac::PSM.frce_off().write_and_wait(|w| { - w.set_sio(true); - w.set_proc1(true); - }); - // clear force_off first, force_on second. The other way around would reset PROC0. - pac::PSM.frce_off().write_and_wait(|_| {}); - pac::PSM.frce_on().write_and_wait(|_| {}); + #[cfg(feature = "rp2040")] + { + pac::PSM.frce_on().write_and_wait(|w| { + w.set_proc0(true); + }); + // Then reset SIO and PROC1. + pac::PSM.frce_off().write_and_wait(|w| { + w.set_sio(true); + w.set_proc1(true); + }); + // clear force_off first, force_on second. The other way around would reset PROC0. + pac::PSM.frce_off().write_and_wait(|_| {}); + pac::PSM.frce_on().write_and_wait(|_| {}); + } + + #[cfg(feature = "_rp235x")] + { + // on RP235x, datasheet says "The FRCE_ON register is a development feature that does nothing in production devices." + // No idea why they removed it. Removing it means we can't use PSM to reset SIO, because it comes before + // PROC0, so we'd need FRCE_ON to prevent resetting ourselves. + // + // So we just unlock the spinlock manually. + pac::SIO.spinlock(31).write_value(1); + + // We can still use PSM to reset PROC1 since it comes after PROC0 in the state machine. + pac::PSM.frce_off().write_and_wait(|w| w.set_proc1(true)); + pac::PSM.frce_off().write_and_wait(|_| {}); + + // Make atomics work between cores. + enable_actlr_extexclall(); + } +} + +/// Set the EXTEXCLALL bit in ACTLR. +/// +/// The default MPU memory map marks all memory as non-shareable, so atomics don't +/// synchronize memory accesses between cores at all. This bit forces all memory to be +/// considered shareable regardless of what the MPU says. +/// +/// TODO: does this interfere somehow if the user wants to use a custom MPU configuration? +/// maybe we need to add a way to disable this? +/// +/// This must be done FOR EACH CORE. +#[cfg(feature = "_rp235x")] +unsafe fn enable_actlr_extexclall() { + (&*cortex_m::peripheral::ICB::PTR).actlr.modify(|w| w | (1 << 29)); } /// Extension trait for PAC regs, adding atomic xor/bitset/bitclear writes. diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index 9f7d77bf5..d10b6837c 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -51,20 +51,24 @@ use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; use crate::interrupt::InterruptExt; use crate::peripherals::CORE1; -use crate::{gpio, install_stack_guard, interrupt, pac}; +use crate::{gpio, install_stack_guard, interrupt, pac, Peri}; const PAUSE_TOKEN: u32 = 0xDEADBEEF; const RESUME_TOKEN: u32 = !0xDEADBEEF; static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); #[inline(always)] -fn core1_setup(stack_bottom: *mut usize) { +unsafe fn core1_setup(stack_bottom: *mut usize) { if install_stack_guard(stack_bottom).is_err() { // currently only happens if the MPU was already set up, which // would indicate that the core is already in use from outside // embassy, somehow. trap if so since we can't deal with that. cortex_m::asm::udf(); } + + #[cfg(feature = "_rp235x")] + crate::enable_actlr_extexclall(); + unsafe { gpio::init(); } @@ -135,7 +139,7 @@ unsafe fn SIO_IRQ_FIFO() { } /// Spawn a function on this core -pub fn spawn_core1(_core1: CORE1, stack: &'static mut Stack, entry: F) +pub fn spawn_core1(_core1: Peri<'static, CORE1>, stack: &'static mut Stack, entry: F) where F: FnOnce() -> bad::Never + Send + 'static, { @@ -148,7 +152,7 @@ where entry: *mut ManuallyDrop, stack_bottom: *mut usize, ) -> ! { - core1_setup(stack_bottom); + unsafe { core1_setup(stack_bottom) }; let entry = unsafe { ManuallyDrop::take(&mut *entry) }; @@ -169,6 +173,13 @@ where interrupt::SIO_IRQ_FIFO.enable() }; + // Enable FPU + #[cfg(all(feature = "_rp235x", has_fpu))] + unsafe { + let p = cortex_m::Peripherals::steal(); + p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22)); + } + entry() } diff --git a/embassy-rp/src/otp.rs b/embassy-rp/src/otp.rs new file mode 100644 index 000000000..6091f71b2 --- /dev/null +++ b/embassy-rp/src/otp.rs @@ -0,0 +1,175 @@ +//! Interface to the RP2350's One Time Programmable Memory + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +use crate::rom_data::otp_access; + +/// The ways in which we can fail to access OTP +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The user passed an invalid index to a function. + InvalidIndex, + /// The hardware refused to let us read this word, probably due to + /// read or write lock set earlier in the boot process. + InvalidPermissions, + /// Modification is impossible based on current state; e.g. + /// attempted to clear an OTP bit. + UnsupportedModification, + /// Value being written is bigger than 24 bits allowed for raw writes. + Overflow, + /// An unexpected failure that contains the exact return code + UnexpectedFailure(i32), +} + +/// OTP read address, using automatic Error Correction. +/// +/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or +/// all-ones on permission failure. Only the first 8 KiB is populated. +pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32; + +/// OTP read address, without using any automatic Error Correction. +/// +/// A 32-bit read returns 24-bits of raw data from the OTP word. +pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32; + +/// How many pages in OTP (post error-correction) +pub const NUM_PAGES: usize = 64; + +/// How many rows in one page in OTP (post error-correction) +pub const NUM_ROWS_PER_PAGE: usize = 64; + +/// How many rows in OTP (post error-correction) +pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE; + +/// 24bit mask for raw writes +pub const RAW_WRITE_BIT_MASK: u32 = 0x00FF_FFFF; + +/// Read one ECC protected word from the OTP +pub fn read_ecc_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // First do a raw read to check permissions + let _ = read_raw_word(row)?; + // One 32-bit read gets us two rows + let offset = row >> 1; + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_BASE.add(offset).read() }; + if (row & 1) == 0 { + Ok(value as u16) + } else { + Ok((value >> 16) as u16) + } +} + +/// Read one raw word from the OTP +/// +/// You get the 24-bit raw value in the lower part of the 32-bit result. +pub fn read_raw_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // One 32-bit read gets us one row + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() }; + if value == 0xFFFF_FFFF { + Err(Error::InvalidPermissions) + } else { + Ok(value) + } +} +/// Write one raw word to the OTP +/// +/// 24 bit value will be written to the OTP +pub fn write_raw_word(row: usize, data: u32) -> Result<(), Error> { + if data > RAW_WRITE_BIT_MASK { + return Err(Error::Overflow); + } + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + let row_with_write_bit = row | 0x00010000; + // # Safety + // + // We checked this row was in range already. + let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 4, row_with_write_bit as u32) }; + if result == 0 { + Ok(()) + } else { + // 5.4.3. API Function Return Codes + let error = match result { + -4 => Error::InvalidPermissions, + -18 => Error::UnsupportedModification, + _ => Error::UnexpectedFailure(result), + }; + Err(error) + } +} + +/// Write one raw word to the OTP with ECC +/// +/// 16 bit value will be written + ECC +pub fn write_ecc_word(row: usize, data: u16) -> Result<(), Error> { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + let row_with_write_and_ecc_bit = row | 0x00030000; + + // # Safety + // + // We checked this row was in range already. + + let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 2, row_with_write_and_ecc_bit as u32) }; + if result == 0 { + Ok(()) + } else { + // 5.4.3. API Function Return Codes + // 5.4.3. API Function Return Codes + let error = match result { + -4 => Error::InvalidPermissions, + -18 => Error::UnsupportedModification, + _ => Error::UnexpectedFailure(result), + }; + Err(error) + } +} + +/// Get the random 64bit chipid from rows 0x0-0x3. +pub fn get_chipid() -> Result { + let w0 = read_ecc_word(0x000)?.to_be_bytes(); + let w1 = read_ecc_word(0x001)?.to_be_bytes(); + let w2 = read_ecc_word(0x002)?.to_be_bytes(); + let w3 = read_ecc_word(0x003)?.to_be_bytes(); + + Ok(u64::from_be_bytes([ + w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} + +/// Get the 128bit private random number from rows 0x4-0xb. +/// +/// This ID is not exposed through the USB PICOBOOT GET_INFO command +/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP +/// access point can read the entirety of page 0, so this value is not +/// meaningfully private unless the USB PICOBOOT interface is disabled via the +//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0 +pub fn get_private_random_number() -> Result { + let w0 = read_ecc_word(0x004)?.to_be_bytes(); + let w1 = read_ecc_word(0x005)?.to_be_bytes(); + let w2 = read_ecc_word(0x006)?.to_be_bytes(); + let w3 = read_ecc_word(0x007)?.to_be_bytes(); + let w4 = read_ecc_word(0x008)?.to_be_bytes(); + let w5 = read_ecc_word(0x009)?.to_be_bytes(); + let w6 = read_ecc_word(0x00a)?.to_be_bytes(); + let w7 = read_ecc_word(0x00b)?.to_be_bytes(); + + Ok(u128::from_be_bytes([ + w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} diff --git a/embassy-rp/src/pio/instr.rs b/embassy-rp/src/pio/instr.rs index 9a44088c6..b15d507de 100644 --- a/embassy-rp/src/pio/instr.rs +++ b/embassy-rp/src/pio/instr.rs @@ -3,99 +3,101 @@ use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestin use crate::pio::{Instance, StateMachine}; -/// Set value of scratch register X. -pub unsafe fn set_x(sm: &mut StateMachine, value: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::X, - bit_count: 32, +impl<'d, PIO: Instance, const SM: usize> StateMachine<'d, PIO, SM> { + /// Set value of scratch register X. + pub unsafe fn set_x(&mut self, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::X, + bit_count: 32, + } + .encode(); + self.tx().push(value); + self.exec_instr(OUT); } - .encode(); - sm.tx().push(value); - sm.exec_instr(OUT); -} -/// Get value of scratch register X. -pub unsafe fn get_x(sm: &mut StateMachine) -> u32 { - const IN: u16 = InstructionOperands::IN { - source: InSource::X, - bit_count: 32, + /// Get value of scratch register X. + pub unsafe fn get_x(&mut self) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::X, + bit_count: 32, + } + .encode(); + self.exec_instr(IN); + self.rx().pull() } - .encode(); - sm.exec_instr(IN); - sm.rx().pull() -} -/// Set value of scratch register Y. -pub unsafe fn set_y(sm: &mut StateMachine, value: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::Y, - bit_count: 32, + /// Set value of scratch register Y. + pub unsafe fn set_y(&mut self, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::Y, + bit_count: 32, + } + .encode(); + self.tx().push(value); + self.exec_instr(OUT); } - .encode(); - sm.tx().push(value); - sm.exec_instr(OUT); -} -/// Get value of scratch register Y. -pub unsafe fn get_y(sm: &mut StateMachine) -> u32 { - const IN: u16 = InstructionOperands::IN { - source: InSource::Y, - bit_count: 32, + /// Get value of scratch register Y. + pub unsafe fn get_y(&mut self) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::Y, + bit_count: 32, + } + .encode(); + self.exec_instr(IN); + + self.rx().pull() } - .encode(); - sm.exec_instr(IN); - sm.rx().pull() -} - -/// Set instruction for pindir destination. -pub unsafe fn set_pindir(sm: &mut StateMachine, data: u8) { - let set: u16 = InstructionOperands::SET { - destination: SetDestination::PINDIRS, - data, + /// Set instruction for pindir destination. + pub unsafe fn set_pindir(&mut self, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINDIRS, + data, + } + .encode(); + self.exec_instr(set); } - .encode(); - sm.exec_instr(set); -} -/// Set instruction for pin destination. -pub unsafe fn set_pin(sm: &mut StateMachine, data: u8) { - let set: u16 = InstructionOperands::SET { - destination: SetDestination::PINS, - data, + /// Set instruction for pin destination. + pub unsafe fn set_pin(&mut self, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINS, + data, + } + .encode(); + self.exec_instr(set); } - .encode(); - sm.exec_instr(set); -} -/// Out instruction for pin destination. -pub unsafe fn set_out_pin(sm: &mut StateMachine, data: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::PINS, - bit_count: 32, + /// Out instruction for pin destination. + pub unsafe fn set_out_pin(&mut self, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINS, + bit_count: 32, + } + .encode(); + self.tx().push(data); + self.exec_instr(OUT); } - .encode(); - sm.tx().push(data); - sm.exec_instr(OUT); -} -/// Out instruction for pindir destination. -pub unsafe fn set_out_pindir(sm: &mut StateMachine, data: u32) { - const OUT: u16 = InstructionOperands::OUT { - destination: OutDestination::PINDIRS, - bit_count: 32, + /// Out instruction for pindir destination. + pub unsafe fn set_out_pindir(&mut self, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINDIRS, + bit_count: 32, + } + .encode(); + self.tx().push(data); + self.exec_instr(OUT); } - .encode(); - sm.tx().push(data); - sm.exec_instr(OUT); -} -/// Jump instruction to address. -pub unsafe fn exec_jmp(sm: &mut StateMachine, to_addr: u8) { - let jmp: u16 = InstructionOperands::JMP { - address: to_addr, - condition: JmpCondition::Always, + /// Jump instruction to address. + pub unsafe fn exec_jmp(&mut self, to_addr: u8) { + let jmp: u16 = InstructionOperands::JMP { + address: to_addr, + condition: JmpCondition::Always, + } + .encode(); + self.exec_instr(jmp); } - .encode(); - sm.exec_instr(jmp); } diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 68b1d6849..ec698d99c 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -5,8 +5,8 @@ use core::pin::Pin as FuturePin; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::{Context, Poll}; -use atomic_polyfill::{AtomicU32, AtomicU8}; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use atomic_polyfill::{AtomicU64, AtomicU8}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::extra::U8; use fixed::FixedU32; @@ -18,7 +18,10 @@ use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; use crate::relocate::RelocatedProgram; use crate::{pac, peripherals, RegExt}; -pub mod instr; +mod instr; + +#[doc(inline)] +pub use pio as program; /// Wakers for interrupts and FIFOs. pub struct Wakers([AtomicWaker; 12]); @@ -50,6 +53,18 @@ pub enum FifoJoin { RxOnly, /// Tx fifo twice as deep. RX fifo disabled TxOnly, + /// Enable random writes (`FJOIN_RX_PUT`) from the state machine (through ISR), + /// and random reads from the system (using [`StateMachine::get_rxf_entry`]). + #[cfg(feature = "_rp235x")] + RxAsStatus, + /// Enable random reads (`FJOIN_RX_GET`) from the state machine (through OSR), + /// and random writes from the system (using [`StateMachine::set_rxf_entry`]). + #[cfg(feature = "_rp235x")] + RxAsControl, + /// FJOIN_RX_PUT | FJOIN_RX_GET: RX can be used as a scratch register, + /// not accesible from the CPU + #[cfg(feature = "_rp235x")] + PioScratch, } /// Shift direction. @@ -220,7 +235,7 @@ impl<'a, 'd, PIO: Instance> Drop for IrqFuture<'a, 'd, PIO> { /// Type representing a PIO pin. pub struct Pin<'l, PIO: Instance> { - pin: PeripheralRef<'l, AnyPin>, + pin: Peri<'l, AnyPin>, pio: PhantomData, } @@ -230,10 +245,10 @@ impl<'l, PIO: Instance> Pin<'l, PIO> { pub fn set_drive_strength(&mut self, strength: Drive) { self.pin.pad_ctrl().modify(|w| { w.set_drive(match strength { - Drive::_2mA => pac::pads::vals::Drive::_2MA, - Drive::_4mA => pac::pads::vals::Drive::_4MA, - Drive::_8mA => pac::pads::vals::Drive::_8MA, - Drive::_12mA => pac::pads::vals::Drive::_12MA, + Drive::_2mA => pac::pads::vals::Drive::_2M_A, + Drive::_4mA => pac::pads::vals::Drive::_4M_A, + Drive::_8mA => pac::pads::vals::Drive::_8M_A, + Drive::_12mA => pac::pads::vals::Drive::_12M_A, }); }); } @@ -345,8 +360,9 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { /// Prepare DMA transfer from RX FIFO. pub fn dma_pull<'a, C: Channel, W: Word>( &'a mut self, - ch: PeripheralRef<'a, C>, + ch: Peri<'a, C>, data: &'a mut [W], + bswap: bool, ) -> Transfer<'a, C> { let pio_no = PIO::PIO_NO; let p = ch.regs(); @@ -364,6 +380,7 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { w.set_chain_to(ch.number()); w.set_incr_read(false); w.set_incr_write(true); + w.set_bswap(bswap); w.set_en(true); }); compiler_fence(Ordering::SeqCst); @@ -432,7 +449,12 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { } /// Prepare a DMA transfer to TX FIFO. - pub fn dma_push<'a, C: Channel, W: Word>(&'a mut self, ch: PeripheralRef<'a, C>, data: &'a [W]) -> Transfer<'a, C> { + pub fn dma_push<'a, C: Channel, W: Word>( + &'a mut self, + ch: Peri<'a, C>, + data: &'a [W], + bswap: bool, + ) -> Transfer<'a, C> { let pio_no = PIO::PIO_NO; let p = ch.regs(); p.read_addr().write_value(data.as_ptr() as u32); @@ -449,6 +471,7 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { w.set_chain_to(ch.number()); w.set_incr_read(true); w.set_incr_write(false); + w.set_bswap(bswap); w.set_en(true); }); compiler_fence(Ordering::SeqCst); @@ -639,7 +662,7 @@ impl<'d, PIO: Instance> Config<'d, PIO> { /// of the program. The state machine is not started. /// /// `side_set` sets the range of pins affected by side-sets. The range must be consecutive. - /// Side-set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// Sideset pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be /// effective. pub fn use_program(&mut self, prog: &LoadedProgram<'d, PIO>, side_set: &[&Pin<'d, PIO>]) { assert!((prog.side_set.bits() - prog.side_set.optional() as u8) as usize == side_set.len()); @@ -730,7 +753,20 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_in_shiftdir(config.shift_in.direction == ShiftDirection::Right); w.set_autopull(config.shift_out.auto_fill); w.set_autopush(config.shift_in.auto_fill); + + #[cfg(feature = "_rp235x")] + { + w.set_fjoin_rx_get( + config.fifo_join == FifoJoin::RxAsControl || config.fifo_join == FifoJoin::PioScratch, + ); + w.set_fjoin_rx_put( + config.fifo_join == FifoJoin::RxAsStatus || config.fifo_join == FifoJoin::PioScratch, + ); + w.set_in_count(config.in_count); + } }); + + #[cfg(feature = "rp2040")] sm.pinctrl().write(|w| { w.set_sideset_count(config.pins.sideset_count); w.set_set_count(config.pins.set_count); @@ -740,9 +776,100 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_set_base(config.pins.set_base); w.set_out_base(config.pins.out_base); }); - if let Some(origin) = config.origin { - unsafe { instr::exec_jmp(self, origin) } + + #[cfg(feature = "_rp235x")] + { + let mut low_ok = true; + let mut high_ok = true; + + let in_pins = config.pins.in_base..config.pins.in_base + config.in_count; + let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count; + let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count; + let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count; + + for pin_range in [in_pins, side_pins, set_pins, out_pins] { + for pin in pin_range { + low_ok &= pin < 32; + high_ok &= pin >= 16; + } + } + + if !low_ok && !high_ok { + panic!( + "All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}", + config.pins.in_base, + config.pins.in_base + config.in_count - 1, + config.pins.sideset_base, + config.pins.sideset_base + config.pins.sideset_count - 1, + config.pins.set_base, + config.pins.set_base + config.pins.set_count - 1, + config.pins.out_base, + config.pins.out_base + config.pins.out_count - 1, + ) + } + let shift = if low_ok { 0 } else { 16 }; + + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default()); + w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default()); + w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default()); + w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default()); + }); + + PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16)); } + + if let Some(origin) = config.origin { + unsafe { self.exec_jmp(origin) } + } + } + + /// Read current instruction address for this state machine + pub fn get_addr(&self) -> u8 { + let addr = Self::this_sm().addr(); + addr.read().addr() + } + + /// Read TX FIFO threshold for this state machine. + pub fn get_tx_threshold(&self) -> u8 { + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.read().pull_thresh() + } + + /// Set/change the TX FIFO threshold for this state machine. + pub fn set_tx_threshold(&mut self, threshold: u8) { + assert!(threshold <= 31); + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_pull_thresh(threshold); + }); + } + + /// Read TX FIFO threshold for this state machine. + pub fn get_rx_threshold(&self) -> u8 { + Self::this_sm().shiftctrl().read().push_thresh() + } + + /// Set/change the RX FIFO threshold for this state machine. + pub fn set_rx_threshold(&mut self, threshold: u8) { + assert!(threshold <= 31); + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_push_thresh(threshold); + }); + } + + /// Set/change both TX and RX FIFO thresholds for this state machine. + pub fn set_thresholds(&mut self, threshold: u8) { + assert!(threshold <= 31); + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_push_thresh(threshold); + w.set_pull_thresh(threshold); + }); } /// Set the clock divider for this state machine. @@ -859,6 +986,20 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { pub fn rx_tx(&mut self) -> (&mut StateMachineRx<'d, PIO, SM>, &mut StateMachineTx<'d, PIO, SM>) { (&mut self.rx, &mut self.tx) } + + /// Return the contents of the nth entry of the RX FIFO + /// (should be used only when the FIFO config is set to [`FifoJoin::RxAsStatus`]) + #[cfg(feature = "_rp235x")] + pub fn get_rxf_entry(&self, n: usize) -> u32 { + PIO::PIO.rxf_putget(SM).putget(n).read() + } + + /// Set the contents of the nth entry of the RX FIFO + /// (should be used only when the FIFO config is set to [`FifoJoin::RxAsControl`]) + #[cfg(feature = "_rp235x")] + pub fn set_rxf_entry(&self, n: usize, val: u32) { + PIO::PIO.rxf_putget(SM).putget(n).write_value(val) + } } /// PIO handle. @@ -945,6 +1086,9 @@ impl<'d, PIO: Instance> Common<'d, PIO> { prog: &Program, origin: u8, ) -> Result, usize> { + #[cfg(not(feature = "_rp235x"))] + assert!(prog.version == pio::PioVersion::V0); + let prog = RelocatedProgram::new_with_origin(prog, origin); let used_memory = self.try_write_instr(prog.origin() as _, prog.code())?; Ok(LoadedProgram { @@ -1003,13 +1147,29 @@ impl<'d, PIO: Instance> Common<'d, PIO> { /// (i.e., have their `FUNCSEL` reset to `NULL`) when the [`Common`] *and* /// all [`StateMachine`]s for this block have been dropped. **Other members /// of [`Pio`] do not keep pin registrations alive.** - pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { - into_ref!(pin); + pub fn make_pio_pin(&mut self, pin: Peri<'d, impl PioPin + 'd>) -> Pin<'d, PIO> { + // enable the outputs + pin.pad_ctrl().write(|w| w.set_od(false)); + // especially important on the 235x, where IE defaults to 0 + pin.pad_ctrl().write(|w| w.set_ie(true)); + pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + // TODO rp235x errata E9 recommends to not enable IE if we're not + // going to use input. Maybe add an API for the user to enable/disable this? + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); // we can be relaxed about this because we're &mut here and nothing is cached PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); Pin { - pin: pin.into_ref().map_into(), + pin: pin.into(), pio: PhantomData::default(), } } @@ -1142,7 +1302,7 @@ pub struct Pio<'d, PIO: Instance> { impl<'d, PIO: Instance> Pio<'d, PIO> { /// Create a new instance of a PIO peripheral. - pub fn new(_pio: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { + pub fn new(_pio: Peri<'d, PIO>, _irq: impl Binding>) -> Self { PIO::state().users.store(5, Ordering::Release); PIO::state().used_pins.store(0, Ordering::Release); PIO::Interrupt::unpend(); @@ -1187,7 +1347,7 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { // other way. pub struct State { users: AtomicU8, - used_pins: AtomicU32, + used_pins: AtomicU64, } fn on_pio_drop() { @@ -1195,8 +1355,7 @@ fn on_pio_drop() { if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { let used_pins = state.used_pins.load(Ordering::Relaxed); let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; - // we only have 30 pins. don't test the other two since gpio() asserts. - for i in 0..30 { + for i in 0..crate::gpio::BANK0_PIN_COUNT { if used_pins & (1 << i) != 0 { pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); } @@ -1211,9 +1370,7 @@ trait SealedInstance { #[inline] fn wakers() -> &'static Wakers { - const NEW_AW: AtomicWaker = AtomicWaker::new(); - static WAKERS: Wakers = Wakers([NEW_AW; 12]); - + static WAKERS: Wakers = Wakers([const { AtomicWaker::new() }; 12]); &WAKERS } @@ -1221,7 +1378,7 @@ trait SealedInstance { fn state() -> &'static State { static STATE: State = State { users: AtomicU8::new(0), - used_pins: AtomicU32::new(0), + used_pins: AtomicU64::new(0), }; &STATE @@ -1230,7 +1387,7 @@ trait SealedInstance { /// PIO instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance + Sized + Unpin { +pub trait Instance: SealedInstance + PeripheralType + Sized + Unpin { /// Interrupt for this peripheral. type Interrupt: crate::interrupt::typelevel::Interrupt; } diff --git a/embassy-rp/src/pio_programs/clock_divider.rs b/embassy-rp/src/pio_programs/clock_divider.rs new file mode 100644 index 000000000..02e353f53 --- /dev/null +++ b/embassy-rp/src/pio_programs/clock_divider.rs @@ -0,0 +1,25 @@ +//! Helper functions for calculating PIO clock dividers + +use fixed::traits::ToFixed; +use fixed::types::extra::U8; + +use crate::clocks::clk_sys_freq; + +/// Calculate a PIO clock divider value based on the desired target frequency. +/// +/// # Arguments +/// +/// * `target_hz` - The desired PIO clock frequency in Hz +/// +/// # Returns +/// +/// A fixed-point divider value suitable for use in a PIO state machine configuration +#[inline] +pub fn calculate_pio_clock_divider(target_hz: u32) -> fixed::FixedU32 { + // Requires a non-zero frequency + assert!(target_hz > 0, "PIO clock frequency cannot be zero"); + + // Calculate the divider + let divider = (clk_sys_freq() + target_hz / 2) / target_hz; + divider.to_fixed() +} diff --git a/embassy-rp/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs new file mode 100644 index 000000000..546c85a89 --- /dev/null +++ b/embassy-rp/src/pio_programs/hd44780.rs @@ -0,0 +1,208 @@ +//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, + StateMachine, +}; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; +use crate::Peri; + +/// This struct represents a HD44780 program that takes command words ( <0:4>) +pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + .side_set 1 opt + .origin 20 + + loop: + out x, 24 + delay: + jmp x--, delay + out pins, 4 side 1 + out null, 4 side 0 + jmp !osre, loop + irq 0 + "#, + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// This struct represents a HD44780 program that takes command sequences ( , data...) +pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + // many side sets are only there to free up a delay bit! + let prg = pio::pio_asm!( + r#" + .origin 27 + .side_set 1 + + .wrap_target + pull side 0 + out x 1 side 0 ; !rs + out y 7 side 0 ; #data - 1 + + ; rs/rw to e: >= 60ns + ; e high time: >= 500ns + ; e low time: >= 500ns + ; read data valid after e falling: ~5ns + ; write data hold after e falling: ~10ns + + loop: + pull side 0 + jmp !x data side 0 + command: + set pins 0b00 side 0 + jmp shift side 0 + data: + set pins 0b01 side 0 + shift: + out pins 4 side 1 [9] + nop side 0 [9] + out pins 4 side 1 [9] + mov osr null side 0 [7] + out pindirs 4 side 0 + set pins 0b10 side 0 + busy: + nop side 1 [9] + jmp pin more side 0 [9] + mov osr ~osr side 1 [9] + nop side 0 [4] + out pindirs 4 side 0 + jmp y-- loop side 0 + .wrap + more: + nop side 1 [9] + jmp busy side 0 [9] + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed HD44780 driver +pub struct PioHD44780<'l, P: Instance, const S: usize> { + dma: Peri<'l, AnyChannel>, + sm: StateMachine<'l, P, S>, + + buf: [u8; 40], +} + +impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { + /// Configure the given state machine to first init, then write data to, a HD44780 display. + pub async fn new( + common: &mut Common<'l, P>, + mut sm: StateMachine<'l, P, S>, + mut irq: Irq<'l, P, S>, + mut dma: Peri<'l, impl Channel>, + rs: Peri<'l, impl PioPin>, + rw: Peri<'l, impl PioPin>, + e: Peri<'l, impl PioPin>, + db4: Peri<'l, impl PioPin>, + db5: Peri<'l, impl PioPin>, + db6: Peri<'l, impl PioPin>, + db7: Peri<'l, impl PioPin>, + word_prg: &PioHD44780CommandWordProgram<'l, P>, + seq_prg: &PioHD44780CommandSequenceProgram<'l, P>, + ) -> PioHD44780<'l, P, S> { + let rs = common.make_pio_pin(rs); + let rw = common.make_pio_pin(rw); + let e = common.make_pio_pin(e); + let db4 = common.make_pio_pin(db4); + let db5 = common.make_pio_pin(db5); + let db6 = common.make_pio_pin(db6); + let db7 = common.make_pio_pin(db7); + + sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); + + let mut cfg = Config::default(); + cfg.use_program(&word_prg.prg, &[&e]); + + // Target 1 MHz PIO clock (each cycle is 1µs) + cfg.clock_divider = calculate_pio_clock_divider(1_000_000); + + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Left, + threshold: 32, + }; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + // init to 8 bit thrice + sm.tx().push((50000 << 8) | 0x30); + sm.tx().push((5000 << 8) | 0x30); + sm.tx().push((200 << 8) | 0x30); + // init 4 bit + sm.tx().push((200 << 8) | 0x20); + // set font and lines + sm.tx().push((50 << 8) | 0x20); + sm.tx().push(0b1100_0000); + + irq.wait().await; + sm.set_enable(false); + + let mut cfg = Config::default(); + cfg.use_program(&seq_prg.prg, &[&e]); + + // Target ~15.6 MHz PIO clock (~64ns/insn) + cfg.clock_divider = calculate_pio_clock_divider(15_600_000); + + cfg.set_jmp_pin(&db7); + cfg.set_set_pins(&[&rs, &rw]); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + + // display on and cursor on and blinking, reset display + sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1], false).await; + + Self { + dma: dma.into(), + sm, + buf: [0x20; 40], + } + } + + /// Write a line to the display + pub async fn add_line(&mut self, s: &[u8]) { + // move cursor to 0:0, prepare 16 characters + self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); + // move line 2 up + self.buf.copy_within(22..38, 3); + // move cursor to 1:0, prepare 16 characters + self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); + // file line 2 with spaces + self.buf[22..38].fill(0x20); + // copy input line + let len = s.len().min(16); + self.buf[22..22 + len].copy_from_slice(&s[0..len]); + // set cursor to 1:15 + self.buf[38..].copy_from_slice(&[0x80, 0xcf]); + + self.sm.tx().dma_push(self.dma.reborrow(), &self.buf, false).await; + } +} diff --git a/embassy-rp/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs new file mode 100644 index 000000000..7ceed3fa6 --- /dev/null +++ b/embassy-rp/src/pio_programs/i2s.rs @@ -0,0 +1,98 @@ +//! Pio backed I2s output + +use fixed::traits::ToFixed; + +use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::Peri; + +/// This struct represents an i2s output driver program +/// +/// The sample bit-depth is set through scratch register `Y`. +/// `Y` has to be set to sample bit-depth - 2. +/// (14 = 16bit, 22 = 24bit, 30 = 32bit) +pub struct PioI2sOutProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioI2sOutProgram<'d, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm!( + ".side_set 2", // side 0bWB - W = Word Clock, B = Bit Clock + " mov x, y side 0b01", // y stores sample depth - 2 (14 = 16bit, 22 = 24bit, 30 = 32bit) + "left_data:", + " out pins, 1 side 0b00", + " jmp x-- left_data side 0b01", + " out pins 1 side 0b10", + " mov x, y side 0b11", + "right_data:", + " out pins 1 side 0b10", + " jmp x-- right_data side 0b11", + " out pins 1 side 0b00", + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed I2s output driver +pub struct PioI2sOut<'d, P: Instance, const S: usize> { + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> { + /// Configure a state machine to output I2s + pub fn new( + common: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + data_pin: Peri<'d, impl PioPin>, + bit_clock_pin: Peri<'d, impl PioPin>, + lr_clock_pin: Peri<'d, impl PioPin>, + sample_rate: u32, + bit_depth: u32, + program: &PioI2sOutProgram<'d, P>, + ) -> Self { + let data_pin = common.make_pio_pin(data_pin); + let bit_clock_pin = common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); + cfg.set_out_pins(&[&data_pin]); + let clock_frequency = sample_rate * bit_depth * 2; + cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / 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 + }; + sm.set_config(&cfg); + sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); + + // Set the `y` register up to configure the sample depth + // The SM counts down to 0 and uses one clock cycle to set up the counter, + // which results in bit_depth - 2 as register value. + unsafe { sm.set_y(bit_depth - 2) }; + + sm.set_enable(true); + + Self { dma: dma.into(), sm } + } + + /// Return an in-prograss dma transfer future. Awaiting it will guarentee a complete transfer. + pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> { + self.sm.tx().dma_push(self.dma.reborrow(), buff, false) + } +} diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs new file mode 100644 index 000000000..8eac328b3 --- /dev/null +++ b/embassy-rp/src/pio_programs/mod.rs @@ -0,0 +1,11 @@ +//! Pre-built pio programs for common interfaces + +pub mod clock_divider; +pub mod hd44780; +pub mod i2s; +pub mod onewire; +pub mod pwm; +pub mod rotary_encoder; +pub mod stepper; +pub mod uart; +pub mod ws2812; diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs new file mode 100644 index 000000000..287ddab41 --- /dev/null +++ b/embassy-rp/src/pio_programs/onewire.rs @@ -0,0 +1,296 @@ +//! OneWire pio driver + +use crate::clocks::clk_sys_freq; +use crate::gpio::Level; +use crate::pio::{ + Common, Config, Direction, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::Peri; + +/// This struct represents a onewire driver program +pub struct PioOneWireProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, + reset_addr: u8, + next_bit_addr: u8, +} + +impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + ; We need to use the pins direction to simulate open drain output + ; This results in all the side-set values being swapped from the actual pin value + .side_set 1 pindirs + + ; Set the origin to 0 so we can correctly use jmp instructions externally + .origin 0 + + ; Tick rate is 1 tick per 6us, so all delays should be calculated back to that + ; All the instructions have a calculated delay XX in us as [(XX / CLK) - 1]. + ; The - 1 is for the instruction which also takes one clock cyle. + ; The delay can be 0 which will result in just 6us for the instruction itself + .define CLK 6 + + ; Write the reset block after trigger + public reset: + set x, 4 side 0 [(60 / CLK) - 1] ; idle before reset + reset_inner: ; Repeat the following 5 times, so 5*96us = 480us in total + nop side 1 [(90 / CLK) - 1] + jmp x--, reset_inner side 1 [( 6 / CLK) - 1] + ; Fallthrough + + ; Check for presence of one or more devices. + ; This samples 32 times with an interval of 12us after a 18us delay. + ; If any bit is zero in the end value, there is a detection + ; This whole function takes 480us + set x, 31 side 0 [(24 / CLK) - 1] ; Loop 32 times -> 32*12us = 384us + presence_check: + in pins, 1 side 0 [( 6 / CLK) - 1] ; poll pin and push to isr + jmp x--, presence_check side 0 [( 6 / CLK) - 1] + jmp next_bit side 0 [(72 / CLK) - 1] + + ; The low pulse was already done, we only need to delay and poll the bit in case we are reading + write_1: + nop side 0 [( 6 / CLK) - 1] ; Delay before sampling the input pin + in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR + ; Fallthrough + + ; This is the entry point when reading and writing data + public next_bit: + .wrap_target + out x, 1 side 0 [(12 / CLK) - 1] ; Stalls if no data available in TX FIFO and OSR + jmp x--, write_1 side 1 [( 6 / CLK) - 1] ; Do the always low part of a bit, jump to write_1 if we want to write a 1 bit + in null, 1 side 1 [(54 / CLK) - 1] ; Do the remainder of the low part of a 0 bit + ; This writes 0 into the ISR so that the shift count stays in sync + .wrap + "# + ); + + Self { + prg: common.load_program(&prg.program), + reset_addr: prg.public_defines.reset as u8, + next_bit_addr: prg.public_defines.next_bit as u8, + } + } +} +/// Pio backed OneWire driver +pub struct PioOneWire<'d, PIO: Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, + cfg: Config<'d, PIO>, + reset_addr: u8, + next_bit_addr: u8, +} + +impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { + /// Create a new instance the driver + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + pin: Peri<'d, impl PioPin>, + program: &PioOneWireProgram<'d, PIO>, + ) -> Self { + let pin = common.make_pio_pin(pin); + + sm.set_pin_dirs(Direction::In, &[&pin]); + sm.set_pins(Level::Low, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&pin]); + cfg.set_in_pins(&[&pin]); + + let shift_cfg = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.shift_in = shift_cfg; + cfg.shift_out = shift_cfg; + + let divider = (clk_sys_freq() / 1000000) as u16 * 6; + cfg.clock_divider = divider.into(); + + sm.set_config(&cfg); + sm.clear_fifos(); + sm.restart(); + unsafe { + sm.exec_jmp(program.next_bit_addr); + } + sm.set_enable(true); + + Self { + sm, + cfg, + reset_addr: program.reset_addr, + next_bit_addr: program.next_bit_addr, + } + } + + /// Perform an initialization sequence, will return true if a presence pulse was detected from a device + pub async fn reset(&mut self) -> bool { + // The state machine immediately starts running when jumping to this address + unsafe { + self.sm.exec_jmp(self.reset_addr); + } + + let rx = self.sm.rx(); + let mut found = false; + for _ in 0..4 { + if rx.wait_pull().await != 0 { + found = true; + } + } + + found + } + + /// Write bytes to the onewire bus + pub async fn write_bytes(&mut self, data: &[u8]) { + let (rx, tx) = self.sm.rx_tx(); + for b in data { + tx.wait_push(*b as u32).await; + + // Empty the buffer that is being filled with every write + let _ = rx.wait_pull().await; + } + } + + /// Read bytes from the onewire bus + pub async fn read_bytes(&mut self, data: &mut [u8]) { + let (rx, tx) = self.sm.rx_tx(); + for b in data { + // Write all 1's so that we can read what the device responds + tx.wait_push(0xff).await; + + *b = (rx.wait_pull().await >> 24) as u8; + } + } + + async fn search(&mut self, state: &mut PioOneWireSearch) -> Option { + if !self.reset().await { + // No device present, no use in searching + state.finished = true; + return None; + } + self.write_bytes(&[0xF0]).await; // 0xF0 is the search rom command + + self.prepare_search(); + + let (rx, tx) = self.sm.rx_tx(); + + let mut value = 0; + let mut last_zero = 0; + + for bit in 0..64 { + // Write 2 dummy bits to read a bit and its complement + tx.wait_push(0x1).await; + tx.wait_push(0x1).await; + let in1 = rx.wait_pull().await; + let in2 = rx.wait_pull().await; + let push = match (in1, in2) { + (0, 0) => { + // If both are 0, it means we have devices with 0 and 1 bits in this position + let write_value = if bit < state.last_discrepancy { + (state.last_rom & (1 << bit)) != 0 + } else { + bit == state.last_discrepancy + }; + + if write_value { + 1 + } else { + last_zero = bit; + 0 + } + } + (0, 1) => 0, // Only devices with a 0 bit in this position + (1, 0) => 1, // Only devices with a 1 bit in this position + _ => { + // If both are 1, it means there is no device active and there is no point in continuing + self.restore_after_search(); + state.finished = true; + return None; + } + }; + value >>= 1; + if push == 1 { + value |= 1 << 63; + } + tx.wait_push(push).await; + let _ = rx.wait_pull().await; // Discard the result of the write action + } + + self.restore_after_search(); + + state.last_discrepancy = last_zero; + state.finished = last_zero == 0; + state.last_rom = value; + Some(value) + } + + fn prepare_search(&mut self) { + self.cfg.shift_in.threshold = 1; + self.cfg.shift_in.direction = ShiftDirection::Left; + self.cfg.shift_out.threshold = 1; + + self.sm.set_enable(false); + self.sm.set_config(&self.cfg); + + // set_config jumps to the wrong address so jump to the right one here + unsafe { + self.sm.exec_jmp(self.next_bit_addr); + } + self.sm.set_enable(true); + } + + fn restore_after_search(&mut self) { + self.cfg.shift_in.threshold = 8; + self.cfg.shift_in.direction = ShiftDirection::Right; + self.cfg.shift_out.threshold = 8; + + self.sm.set_enable(false); + self.sm.set_config(&self.cfg); + + // Clear the state in case we aborted prematurely with some bits still in the shift registers + self.sm.clear_fifos(); + self.sm.restart(); + + // set_config jumps to the wrong address so jump to the right one here + unsafe { + self.sm.exec_jmp(self.next_bit_addr); + } + self.sm.set_enable(true); + } +} + +/// Onewire search state +pub struct PioOneWireSearch { + last_rom: u64, + last_discrepancy: u8, + finished: bool, +} + +impl PioOneWireSearch { + /// Create a new Onewire search state + pub fn new() -> Self { + Self { + last_rom: 0, + last_discrepancy: 0, + finished: false, + } + } + + /// Search for the next address on the bus + pub async fn next(&mut self, pio: &mut PioOneWire<'_, PIO, SM>) -> Option { + if self.finished { + None + } else { + pio.search(self).await + } + } + + /// Is finished when all devices have been found + pub fn is_finished(&self) -> bool { + self.finished + } +} diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs new file mode 100644 index 000000000..f0f837bc5 --- /dev/null +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -0,0 +1,121 @@ +//! PIO backed PWM driver + +use core::time::Duration; + +use pio::InstructionOperands; + +use crate::gpio::Level; +use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine}; +use crate::{clocks, Peri}; + +/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time +fn to_pio_cycles(duration: Duration) -> u32 { + (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow +} + +/// This struct represents a PWM program loaded into pio instruction memory. +pub struct PioPwmProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( + ".side_set 1 opt" + "pull noblock side 0" + "mov x, osr" + "mov y, isr" + "countloop:" + "jmp x!=y noset" + "jmp skip side 1" + "noset:" + "nop" + "skip:" + "jmp y-- countloop" + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed PWM output +pub struct PioPwm<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, + pin: Pin<'d, T>, +} + +impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { + /// Configure a state machine as a PWM output + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin: Peri<'d, impl PioPin>, + program: &PioPwmProgram<'d, T>, + ) -> Self { + let pin = pio.make_pio_pin(pin); + sm.set_pins(Level::High, &[&pin]); + sm.set_pin_dirs(Direction::Out, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&pin]); + + sm.set_config(&cfg); + + Self { sm, pin } + } + + /// Enable's the PIO program, continuing the wave generation from the PIO program. + pub fn start(&mut self) { + self.sm.set_enable(true); + } + + /// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO. + pub fn stop(&mut self) { + self.sm.set_enable(false); + } + + /// Sets the pwm period, which is the length of time for each pio wave until reset. + pub fn set_period(&mut self, duration: Duration) { + let is_enabled = self.sm.is_enabled(); + while !self.sm.tx().empty() {} // Make sure that the queue is empty + self.sm.set_enable(false); + self.sm.tx().push(to_pio_cycles(duration)); + unsafe { + self.sm.exec_instr( + InstructionOperands::PULL { + if_empty: false, + block: false, + } + .encode(), + ); + self.sm.exec_instr( + InstructionOperands::OUT { + destination: ::pio::OutDestination::ISR, + bit_count: 32, + } + .encode(), + ); + }; + if is_enabled { + self.sm.set_enable(true) // Enable if previously enabled + } + } + + /// Set the number of pio cycles to set the wave on high to. + pub fn set_level(&mut self, level: u32) { + self.sm.tx().push(level); + } + + /// Set the pulse width high time + pub fn write(&mut self, duration: Duration) { + self.set_level(to_pio_cycles(duration)); + } + + /// Return the state machine and pin. + pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) { + (self.sm, self.pin) + } +} diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs new file mode 100644 index 000000000..70b3795e9 --- /dev/null +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -0,0 +1,78 @@ +//! PIO backed quadrature encoder + +use crate::gpio::Pull; +use crate::pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, +}; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; +use crate::Peri; + +/// This struct represents an Encoder program loaded into pio instruction memory. +pub struct PioEncoderProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio Backed quadrature encoder reader +pub struct PioEncoder<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { + /// Configure a state machine with the loaded [PioEncoderProgram] + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin_a: Peri<'d, impl PioPin>, + pin_b: Peri<'d, impl PioPin>, + program: &PioEncoderProgram<'d, T>, + ) -> Self { + let mut pin_a = pio.make_pio_pin(pin_a); + let mut pin_b = pio.make_pio_pin(pin_b); + pin_a.set_pull(Pull::Up); + pin_b.set_pull(Pull::Up); + sm.set_pin_dirs(PioDirection::In, &[&pin_a, &pin_b]); + + let mut cfg = Config::default(); + cfg.set_in_pins(&[&pin_a, &pin_b]); + cfg.fifo_join = FifoJoin::RxOnly; + cfg.shift_in.direction = ShiftDirection::Left; + + // Target 12.5 KHz PIO clock + cfg.clock_divider = calculate_pio_clock_divider(12_500); + + cfg.use_program(&program.prg, &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Read a single count from the encoder + pub async fn read(&mut self) -> Direction { + loop { + match self.sm.rx().wait_pull().await { + 0 => return Direction::CounterClockwise, + 1 => return Direction::Clockwise, + _ => {} + } + } + } +} + +/// Encoder Count Direction +pub enum Direction { + /// Encoder turned clockwise + Clockwise, + /// Encoder turned counter clockwise + CounterClockwise, +} diff --git a/examples/rp23/src/bin/pio_stepper.rs b/embassy-rp/src/pio_programs/stepper.rs similarity index 53% rename from examples/rp23/src/bin/pio_stepper.rs rename to embassy-rp/src/pio_programs/stepper.rs index 8b52dc37a..0e9a8daf9 100644 --- a/examples/rp23/src/bin/pio_stepper.rs +++ b/embassy-rp/src/pio_programs/stepper.rs @@ -1,56 +1,20 @@ -//! This example shows how to use the PIO module in the RP2040 to implement a stepper motor driver -//! for a 5-wire stepper such as the 28BYJ-48. You can halt an ongoing rotation by dropping the future. +//! Pio Stepper Driver for 5-wire steppers -#![no_std] -#![no_main] use core::mem::{self, MaybeUninit}; -use defmt::info; -use embassy_executor::Spawner; -use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; -use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; -use embassy_time::{with_timeout, Duration, Timer}; -use fixed::traits::ToFixed; -use fixed::types::extra::U8; -use fixed::FixedU32; -use {defmt_rtt as _, panic_probe as _}; +use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; +use crate::Peri; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -bind_interrupts!(struct Irqs { - PIO0_IRQ_0 => InterruptHandler; -}); - -pub struct PioStepper<'d, T: Instance, const SM: usize> { - irq: Irq<'d, T, SM>, - sm: StateMachine<'d, T, SM>, +/// This struct represents a Stepper driver program loaded into pio instruction memory. +pub struct PioStepperProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, } -impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - irq: Irq<'d, T, SM>, - pin0: impl PioPin, - pin1: impl PioPin, - pin2: impl PioPin, - pin3: impl PioPin, - ) -> Self { - let prg = pio_proc::pio_asm!( +impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( "pull block", "mov x, osr", "pull block", @@ -65,6 +29,31 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { "end:", "irq 0 rel" ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed Stepper driver +pub struct PioStepper<'d, T: Instance, const SM: usize> { + irq: Irq<'d, T, SM>, + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { + /// Configure a state machine to drive a stepper + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + irq: Irq<'d, T, SM>, + pin0: Peri<'d, impl PioPin>, + pin1: Peri<'d, impl PioPin>, + pin2: Peri<'d, impl PioPin>, + pin3: Peri<'d, impl PioPin>, + program: &PioStepperProgram<'d, T>, + ) -> Self { let pin0 = pio.make_pio_pin(pin0); let pin1 = pio.make_pio_pin(pin1); let pin2 = pio.make_pio_pin(pin2); @@ -72,23 +61,27 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); let mut cfg = Config::default(); cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); - cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); - cfg.use_program(&pio.load_program(&prg.program), &[]); + + cfg.clock_divider = calculate_pio_clock_divider(100 * 136); + + cfg.use_program(&program.prg, &[]); sm.set_config(&cfg); sm.set_enable(true); Self { irq, sm } } - // Set pulse frequency + /// Set pulse frequency pub fn set_frequency(&mut self, freq: u32) { - let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); - assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); - assert!(clock_divider >= 1, "clkdiv must be >= 1"); + let clock_divider = calculate_pio_clock_divider(freq * 136); + let divider_f32 = clock_divider.to_num::(); + assert!(divider_f32 <= 65536.0, "clkdiv must be <= 65536"); + assert!(divider_f32 >= 1.0, "clkdiv must be >= 1"); + self.sm.set_clock_divider(clock_divider); self.sm.clkdiv_restart(); } - // Full step, one phase + /// Full step, one phase pub async fn step(&mut self, steps: i32) { if steps > 0 { self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await @@ -97,7 +90,7 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { } } - // Full step, two phase + /// Full step, two phase pub async fn step2(&mut self, steps: i32) { if steps > 0 { self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await @@ -106,7 +99,7 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { } } - // Half step + /// Half step pub async fn step_half(&mut self, steps: i32) { if steps > 0 { self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await @@ -154,30 +147,3 @@ impl Drop for OnDrop { unsafe { self.f.as_ptr().read()() } } } - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let Pio { - mut common, irq0, sm0, .. - } = Pio::new(p.PIO0, Irqs); - - let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); - stepper.set_frequency(120); - loop { - info!("CW full steps"); - stepper.step(1000).await; - - info!("CCW full steps, drop after 1 sec"); - if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { - info!("Time's up!"); - Timer::after(Duration::from_secs(1)).await; - } - - info!("CW half steps"); - stepper.step_half(1000).await; - - info!("CCW half steps"); - stepper.step_half(-1000).await; - } -} diff --git a/embassy-rp/src/pio_programs/uart.rs b/embassy-rp/src/pio_programs/uart.rs new file mode 100644 index 000000000..04e39a571 --- /dev/null +++ b/embassy-rp/src/pio_programs/uart.rs @@ -0,0 +1,186 @@ +//! Pio backed uart drivers + +use core::convert::Infallible; + +use embedded_io_async::{ErrorType, Read, Write}; +use fixed::traits::ToFixed; + +use crate::clocks::clk_sys_freq; +use crate::gpio::Level; +use crate::pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, +}; +use crate::Peri; + +/// This struct represents a uart tx program loaded into pio instruction memory. +pub struct PioUartTxProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioUartTxProgram<'d, PIO> { + /// Load the uart tx program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + .side_set 1 opt + + ; An 8n1 UART transmit program. + ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. + + pull side 1 [7] ; Assert stop bit, or stall with line in idle state + set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks + bitloop: ; This loop will run 8 times (8n1 UART) + out pins, 1 ; Shift 1 bit from OSR to the first OUT pin + jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart transmitter +pub struct PioUartTx<'d, PIO: Instance, const SM: usize> { + sm_tx: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> PioUartTx<'d, PIO, SM> { + /// Configure a pio state machine to use the loaded tx program. + pub fn new( + baud: u32, + common: &mut Common<'d, PIO>, + mut sm_tx: StateMachine<'d, PIO, SM>, + tx_pin: Peri<'d, impl PioPin>, + program: &PioUartTxProgram<'d, PIO>, + ) -> Self { + let tx_pin = common.make_pio_pin(tx_pin); + sm_tx.set_pins(Level::High, &[&tx_pin]); + sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]); + + let mut cfg = Config::default(); + + cfg.set_out_pins(&[&tx_pin]); + cfg.use_program(&program.prg, &[&tx_pin]); + cfg.shift_out.auto_fill = false; + cfg.shift_out.direction = ShiftDirection::Right; + cfg.fifo_join = FifoJoin::TxOnly; + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + sm_tx.set_config(&cfg); + sm_tx.set_enable(true); + + Self { sm_tx } + } + + /// Write a single u8 + pub async fn write_u8(&mut self, data: u8) { + self.sm_tx.tx().wait_push(data as u32).await; + } +} + +impl ErrorType for PioUartTx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Write for PioUartTx<'_, PIO, SM> { + async fn write(&mut self, buf: &[u8]) -> Result { + for byte in buf { + self.write_u8(*byte).await; + } + Ok(buf.len()) + } +} + +/// This struct represents a Uart Rx program loaded into pio instruction memory. +pub struct PioUartRxProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioUartRxProgram<'d, PIO> { + /// Load the uart rx program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and + ; break conditions more gracefully. + ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. + + start: + wait 0 pin 0 ; Stall until start bit is asserted + set x, 7 [10] ; Preload bit counter, then delay until halfway through + rx_bitloop: ; the first data bit (12 cycles incl wait, set). + in pins, 1 ; Shift data bit into ISR + jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles + jmp pin good_rx_stop ; Check stop bit (should be high) + + irq 4 rel ; Either a framing error or a break. Set a sticky flag, + wait 1 pin 0 ; and wait for line to return to idle state. + jmp start ; Don't push data if we didn't see good framing. + + good_rx_stop: ; No delay before returning to start; a little slack is + in null 24 + push ; important in case the TX clock is slightly too fast. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart reciever +pub struct PioUartRx<'d, PIO: Instance, const SM: usize> { + sm_rx: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> PioUartRx<'d, PIO, SM> { + /// Configure a pio state machine to use the loaded rx program. + pub fn new( + baud: u32, + common: &mut Common<'d, PIO>, + mut sm_rx: StateMachine<'d, PIO, SM>, + rx_pin: Peri<'d, impl PioPin>, + program: &PioUartRxProgram<'d, PIO>, + ) -> Self { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[]); + + let rx_pin = common.make_pio_pin(rx_pin); + sm_rx.set_pins(Level::High, &[&rx_pin]); + cfg.set_in_pins(&[&rx_pin]); + cfg.set_jmp_pin(&rx_pin); + sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]); + + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + cfg.shift_in.auto_fill = false; + cfg.shift_in.direction = ShiftDirection::Right; + cfg.shift_in.threshold = 32; + cfg.fifo_join = FifoJoin::RxOnly; + sm_rx.set_config(&cfg); + sm_rx.set_enable(true); + + Self { sm_rx } + } + + /// Wait for a single u8 + pub async fn read_u8(&mut self) -> u8 { + self.sm_rx.rx().wait_pull().await as u8 + } +} + +impl ErrorType for PioUartRx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Read for PioUartRx<'_, PIO, SM> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + while i < buf.len() { + buf[i] = self.read_u8().await; + i += 1; + } + Ok(i) + } +} diff --git a/embassy-rp/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs new file mode 100644 index 000000000..37dd1c4e0 --- /dev/null +++ b/embassy-rp/src/pio_programs/ws2812.rs @@ -0,0 +1,179 @@ +//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use embassy_time::Timer; +use fixed::types::U24F8; +use smart_leds::{RGB8, RGBW}; + +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::Peri; + +const T1: u8 = 2; // start bit +const T2: u8 = 5; // data bit +const T3: u8 = 3; // stop bit +const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; + +/// This struct represents a ws2812 program loaded into pio instruction memory. +pub struct PioWs2812Program<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> { + /// Load the ws2812 program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let side_set = pio::SideSet::new(false, 1, false); + let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); + + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + let mut do_zero = a.label(); + a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); + a.bind(&mut wrap_target); + // Do stop bit + a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); + // Do start bit + a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); + // Do data bit = 1 + a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); + a.bind(&mut do_zero); + // Do data bit = 0 + a.nop_with_delay_and_side_set(T2 - 1, 0); + a.bind(&mut wrap_source); + + let prg = a.assemble_with_wrap(wrap_source, wrap_target); + let prg = common.load_program(&prg); + + Self { prg } + } +} + +/// Pio backed RGB ws2812 driver +/// Const N is the number of ws2812 leds attached to this pin +pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize> { + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { + /// Configure a pio state machine to use the loaded ws2812 program. + pub fn new( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + pin: Peri<'d, impl PioPin>, + program: &PioWs2812Program<'d, P>, + ) -> Self { + // Setup sm0 + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + cfg.use_program(&program.prg, &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); + let ws2812_freq = U24F8::from_num(800); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 24, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { dma: dma.into(), sm } + } + + /// Write a buffer of [smart_leds::RGB8] to the ws2812 string + pub async fn write(&mut self, colors: &[RGB8; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); + words[i] = word; + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await; + + Timer::after_micros(55).await; + } +} + +/// Pio backed RGBW ws2812 driver +/// This version is intended for ws2812 leds with 4 addressable lights +/// Const N is the number of ws2812 leds attached to this pin +pub struct RgbwPioWs2812<'d, P: Instance, const S: usize, const N: usize> { + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize, const N: usize> RgbwPioWs2812<'d, P, S, N> { + /// Configure a pio state machine to use the loaded ws2812 program. + pub fn new( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + pin: Peri<'d, impl PioPin>, + program: &PioWs2812Program<'d, P>, + ) -> Self { + // Setup sm0 + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + cfg.use_program(&program.prg, &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); + let ws2812_freq = U24F8::from_num(800); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { dma: dma.into(), sm } + } + + /// Write a buffer of [smart_leds::RGBW] to the ws2812 string + pub async fn write(&mut self, colors: &[RGBW; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + let word = (u32::from(colors[i].g) << 24) + | (u32::from(colors[i].r) << 16) + | (u32::from(colors[i].b) << 8) + | u32::from(colors[i].a.0); + words[i] = word; + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await; + + Timer::after_micros(55).await; + } +} diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index 7da3dccb0..1e1ccc4c6 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -1,6 +1,8 @@ //! Pulse Width Modulation (PWM) -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; +pub use embedded_hal_1::pwm::SetDutyCycle; +use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; use fixed::traits::ToFixed; use fixed::FixedU16; use pac::pwm::regs::{ChDiv, Intr}; @@ -80,18 +82,57 @@ impl From for Divmode { } } +/// PWM error. +#[derive(Debug)] +pub enum PwmError { + /// Invalid Duty Cycle. + InvalidDutyCycle, +} + +impl Error for PwmError { + fn kind(&self) -> ErrorKind { + match self { + PwmError::InvalidDutyCycle => ErrorKind::Other, + } + } +} + /// PWM driver. pub struct Pwm<'d> { - pin_a: Option>, - pin_b: Option>, + pin_a: Option>, + pin_b: Option>, slice: usize, } +impl<'d> ErrorType for Pwm<'d> { + type Error = PwmError; +} + +impl<'d> SetDutyCycle for Pwm<'d> { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = self.max_duty_cycle(); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + p.cc().modify(|w| { + w.set_a(duty); + w.set_b(duty); + }); + Ok(()) + } +} + impl<'d> Pwm<'d> { fn new_inner( slice: usize, - a: Option>, - b: Option>, + a: Option>, + b: Option>, b_pull: Pull, config: Config, divmode: Divmode, @@ -106,12 +147,21 @@ impl<'d> Pwm<'d> { if let Some(pin) = &a { pin.gpio().ctrl().write(|w| w.set_funcsel(4)); + #[cfg(feature = "_rp235x")] + pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }); } if let Some(pin) = &b { pin.gpio().ctrl().write(|w| w.set_funcsel(4)); pin.pad_ctrl().modify(|w| { #[cfg(feature = "_rp235x")] w.set_iso(false); + #[cfg(feature = "_rp235x")] + if divmode != Divmode::DIV { + // Is in input mode and so must enable input mode for the pin + w.set_ie(true); + } w.set_pue(b_pull == Pull::Up); w.set_pde(b_pull == Pull::Down); }); @@ -126,60 +176,34 @@ impl<'d> Pwm<'d> { /// Create PWM driver without any configured pins. #[inline] - pub fn new_free(slice: impl Peripheral

+ 'd, config: Config) -> Self { - into_ref!(slice); + pub fn new_free(slice: Peri<'d, T>, config: Config) -> Self { Self::new_inner(slice.number(), None, None, Pull::None, config, Divmode::DIV) } /// Create PWM driver with a single 'a' pin as output. #[inline] - pub fn new_output_a( - slice: impl Peripheral

+ 'd, - a: impl Peripheral

> + 'd, - config: Config, - ) -> Self { - into_ref!(slice, a); - Self::new_inner( - slice.number(), - Some(a.map_into()), - None, - Pull::None, - config, - Divmode::DIV, - ) + pub fn new_output_a(slice: Peri<'d, T>, a: Peri<'d, impl ChannelAPin>, config: Config) -> Self { + Self::new_inner(slice.number(), Some(a.into()), None, Pull::None, config, Divmode::DIV) } /// Create PWM driver with a single 'b' pin as output. #[inline] - pub fn new_output_b( - slice: impl Peripheral

+ 'd, - b: impl Peripheral

> + 'd, - config: Config, - ) -> Self { - into_ref!(slice, b); - Self::new_inner( - slice.number(), - None, - Some(b.map_into()), - Pull::None, - config, - Divmode::DIV, - ) + pub fn new_output_b(slice: Peri<'d, T>, b: Peri<'d, impl ChannelBPin>, config: Config) -> Self { + Self::new_inner(slice.number(), None, Some(b.into()), Pull::None, config, Divmode::DIV) } /// Create PWM driver with a 'a' and 'b' pins as output. #[inline] pub fn new_output_ab( - slice: impl Peripheral

+ 'd, - a: impl Peripheral

> + 'd, - b: impl Peripheral

> + 'd, + slice: Peri<'d, T>, + a: Peri<'d, impl ChannelAPin>, + b: Peri<'d, impl ChannelBPin>, config: Config, ) -> Self { - into_ref!(slice, a, b); Self::new_inner( slice.number(), - Some(a.map_into()), - Some(b.map_into()), + Some(a.into()), + Some(b.into()), Pull::None, config, Divmode::DIV, @@ -189,31 +213,29 @@ impl<'d> Pwm<'d> { /// Create PWM driver with a single 'b' as input pin. #[inline] pub fn new_input( - slice: impl Peripheral

+ 'd, - b: impl Peripheral

> + 'd, + slice: Peri<'d, T>, + b: Peri<'d, impl ChannelBPin>, b_pull: Pull, mode: InputMode, config: Config, ) -> Self { - into_ref!(slice, b); - Self::new_inner(slice.number(), None, Some(b.map_into()), b_pull, config, mode.into()) + Self::new_inner(slice.number(), None, Some(b.into()), b_pull, config, mode.into()) } /// Create PWM driver with a 'a' and 'b' pins in the desired input mode. #[inline] pub fn new_output_input( - slice: impl Peripheral

+ 'd, - a: impl Peripheral

> + 'd, - b: impl Peripheral

> + 'd, + slice: Peri<'d, T>, + a: Peri<'d, impl ChannelAPin>, + b: Peri<'d, impl ChannelBPin>, b_pull: Pull, mode: InputMode, config: Config, ) -> Self { - into_ref!(slice, a, b); Self::new_inner( slice.number(), - Some(a.map_into()), - Some(b.map_into()), + Some(a.into()), + Some(b.into()), b_pull, config, mode.into(), @@ -299,6 +321,119 @@ impl<'d> Pwm<'d> { fn bit(&self) -> u32 { 1 << self.slice as usize } + + /// Splits the PWM driver into separate `PwmOutput` instances for channels A and B. + #[inline] + pub fn split(mut self) -> (Option>, Option>) { + ( + self.pin_a + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin), self.slice.clone(), true)), + self.pin_b + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)), + ) + } + /// Splits the PWM driver by reference to allow for separate duty cycle control + /// of each channel (A and B) without taking ownership of the PWM instance. + #[inline] + pub fn split_by_ref(&mut self) -> (Option>, Option>) { + ( + self.pin_a + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin.reborrow()), self.slice.clone(), false)), + self.pin_b + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin.reborrow()), self.slice.clone(), false)), + ) + } +} + +enum PwmChannelPin<'d> { + A(Peri<'d, AnyPin>), + B(Peri<'d, AnyPin>), +} + +/// Single channel of Pwm driver. +pub struct PwmOutput<'d> { + //pin that can be ether ChannelAPin or ChannelBPin + channel_pin: PwmChannelPin<'d>, + slice: usize, + is_owned: bool, +} + +impl<'d> PwmOutput<'d> { + fn new(channel_pin: PwmChannelPin<'d>, slice: usize, is_owned: bool) -> Self { + Self { + channel_pin, + slice, + is_owned, + } + } +} + +impl<'d> Drop for PwmOutput<'d> { + fn drop(&mut self) { + if self.is_owned { + let p = pac::PWM.ch(self.slice); + match &self.channel_pin { + PwmChannelPin::A(pin) => { + p.cc().modify(|w| { + w.set_a(0); + }); + + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + //Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + PwmChannelPin::B(pin) => { + p.cc().modify(|w| { + w.set_b(0); + }); + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + //Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + } + } + } +} + +impl<'d> ErrorType for PwmOutput<'d> { + type Error = PwmError; +} + +impl<'d> SetDutyCycle for PwmOutput<'d> { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = self.max_duty_cycle(); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + match self.channel_pin { + PwmChannelPin::A(_) => { + p.cc().modify(|w| { + w.set_a(duty); + }); + } + PwmChannelPin::B(_) => { + p.cc().modify(|w| { + w.set_b(duty); + }); + } + } + + Ok(()) + } } /// Batch representation of PWM slices. @@ -329,9 +464,22 @@ impl<'d> Drop for Pwm<'d> { pac::PWM.ch(self.slice).csr().write_clear(|w| w.set_en(false)); if let Some(pin) = &self.pin_a { pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + // Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); } if let Some(pin) = &self.pin_b { pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + #[cfg(feature = "_rp235x")] + // Disable input mode. Only pin_b can be input, so not needed for pin_a + pin.pad_ctrl().modify(|w| { + w.set_ie(false); + }); + // Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); } } } @@ -340,7 +488,7 @@ trait SealedSlice {} /// PWM Slice. #[allow(private_bounds)] -pub trait Slice: Peripheral

+ SealedSlice + Sized + 'static { +pub trait Slice: PeripheralType + SealedSlice + Sized + 'static { /// Slice number. fn number(&self) -> usize; } diff --git a/embassy-rp/src/rom_data/mod.rs b/embassy-rp/src/rom_data/mod.rs new file mode 100644 index 000000000..e5fcf8e3c --- /dev/null +++ b/embassy-rp/src/rom_data/mod.rs @@ -0,0 +1,33 @@ +#![cfg_attr( + feature = "rp2040", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. +" +)] +#![cfg_attr( + feature = "_rp235x", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device +" +)] + +#[cfg_attr(feature = "rp2040", path = "rp2040.rs")] +#[cfg_attr(feature = "_rp235x", path = "rp235x.rs")] +mod inner; +pub use inner::*; diff --git a/embassy-rp/src/rom_data.rs b/embassy-rp/src/rom_data/rp2040.rs similarity index 99% rename from embassy-rp/src/rom_data.rs rename to embassy-rp/src/rom_data/rp2040.rs index baebe5b6c..5a74eddd6 100644 --- a/embassy-rp/src/rom_data.rs +++ b/embassy-rp/src/rom_data/rp2040.rs @@ -189,7 +189,7 @@ macro_rules! rom_functions { declare_rom_function! { $(#[$outer])* fn $name( $($argname: $ty),* ) -> $ret { - $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) } } @@ -205,7 +205,7 @@ macro_rules! rom_functions { declare_rom_function! { $(#[$outer])* unsafe fn $name( $($argname: $ty),* ) -> $ret { - $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) } } diff --git a/embassy-rp/src/rom_data/rp235x.rs b/embassy-rp/src/rom_data/rp235x.rs new file mode 100644 index 000000000..b16fee8f7 --- /dev/null +++ b/embassy-rp/src/rom_data/rp235x.rs @@ -0,0 +1,752 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for the tag which matches the mask. +type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +#[allow(unused)] +mod rt_flags { + pub const FUNC_RISCV: u32 = 0x0001; + pub const FUNC_RISCV_FAR: u32 = 0x0003; + pub const FUNC_ARM_SEC: u32 = 0x0004; + // reserved for 32-bit pointer: 0x0008 + pub const FUNC_ARM_NONSEC: u32 = 0x0010; + // reserved for 32-bit pointer: 0x0020 + pub const DATA: u32 = 0x0040; + // reserved for 32-bit pointer: 0x0080 + #[cfg(all(target_arch = "arm", target_os = "none"))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC; + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV; +} + +/// Retrieve rom content from a table using a code. +pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_TABLE_LOOKUP_A1.read() as usize + } else { + ROM_TABLE_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// Retrieve rom data content from a table using a code. +pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_DATA_LOOKUP_A1.read() as usize + } else { + ROM_DATA_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the rp235x datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +// **************** 5.5.7 Low-level Flash Commands **************** + +declare_rom_function! { + /// Restore all QSPI pad controls to their default state, and connect the + /// QMI peripheral to the QSPI pads. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn connect_internal_flash() -> () { + crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Initialise the QMI for serial operations (direct mode) + /// + /// Also initialise a basic XIP mode, where the QMI will perform 03h serial + /// read commands at low speed (CLKDIV=12) in response to XIP reads. + /// + /// Then, issue a sequence to the QSPI device on chip select 0, designed to + /// return it from continuous read mode ("XIP mode") and/or QPI mode to a + /// state where it will accept serial commands. This is necessary after + /// system reset to restore the QSPI device to a known state, because + /// resetting RP2350 does not reset attached QSPI devices. It is also + /// necessary when user code, having already performed some + /// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI + /// device to a state where it will accept the serial erase and programming + /// commands issued by the bootrom’s flash access functions. + /// + /// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO, + /// then the XIP exit sequence is also issued to chip select 1. + /// + /// The QSPI device should be accessible for XIP reads after calling this + /// function; the name flash_exit_xip refers to returning the QSPI device + /// from its XIP state to a serial command state. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_exit_xip() -> () { + crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Erase count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the + /// size of the block erased by this command — this function will use the + /// larger block erase where possible, for much higher erase speed. addr + /// must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API, which can be achieved by calling connect_internal_flash() followed + /// by flash_exit_xip(). After the erase, the flash cache should be flushed + /// via flash_flush_cache() to ensure the modified flash data is visible to + /// cached XIP accesses. + /// + /// Finally, the original XIP mode should be restored by copying the saved + /// XIP setup function from bootram into SRAM, and executing it: the bootrom + /// provides a default function which restores the flash mode/clkdiv + /// discovered during flash scanning, and user programs can override this + /// with their own XIP setup function. + /// + /// For the duration of the erase operation, QMI is in direct mode (Section + /// 12.14.5) and attempting to access XIP from DMA, the debugger or the + /// other core will return a bus fault. XIP becomes accessible again once + /// the function returns. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () { + crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Program data to a range of flash storage addresses starting at addr + /// (offset from the start of flash) and count bytes in size. + /// + /// `addr` must be aligned to a 256-byte boundary, and count must be a + /// multiple of 256. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API — see notes on flash_range_erase(). + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () { + crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Flush the entire XIP cache, by issuing an invalidate by set/way + /// maintenance operation to every cache line (Section 4.4.1). + /// + /// This ensures that flash program/erase operations are visible to + /// subsequent cached XIP reads. + /// + /// Note that this unpins pinned cache lines, which may interfere with + /// cache-as-SRAM use of the XIP cache. + /// + /// No other operations are performed. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_flush_cache() -> () { + crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure the QMI to generate a standard 03h serial read command, with + /// 24 address bits, upon each XIP access. + /// + /// This is a slow XIP configuration, but is widely supported. CLKDIV is set + /// to 12. The debugger may call this function to ensure that flash is + /// readable following a program/erase operation. + /// + /// Note that the same setup is performed by flash_exit_xip(), and the + /// RP2350 flash program/erase functions do not leave XIP in an inaccessible + /// state, so calls to this function are largely redundant. It is provided + /// for compatibility with RP2040. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_enter_cmd_xip() -> () { + crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure QMI for one of a small menu of XIP read modes supported by the + /// bootrom. This mode is configured for both memory windows (both chip + /// selects), and the clock divisor is also applied to direct mode. + /// + /// The available modes are: + /// + /// * 0: `03h` serial read: serial address, serial data, no wait cycles + /// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles + /// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles + /// (including MODE bits, which are driven to 0) + /// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles + /// (including MODE bits, which are driven to 0) + /// + /// The XIP write command/format are not configured by this function. When + /// booting from flash, the bootrom tries each of these modes in turn, from + /// 3 down to 0. The first mode that is found to work is remembered, and a + /// default XIP setup function is written into bootram that calls this + /// function (flash_select_xip_read_mode) with the parameters discovered + /// during flash scanning. This can be called at any time to restore the + /// flash parameters discovered during flash boot. + /// + /// All XIP modes configured by the bootrom have an 8-bit serial command + /// prefix, so that the flash can remain in a serial command state, meaning + /// XIP accesses can be mixed more freely with program/erase serial + /// operations. This has a performance penalty, so users can perform their + /// own flash setup after flash boot using continuous read mode or QPI mode + /// to avoid or alleviate the command prefix cost. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () { + crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Restore the QMI address translation registers, ATRANS0 through ATRANS7, + /// to their reset state. This makes the runtime- to-storage address map an + /// identity map, i.e. the mapped and unmapped address are equal, and the + /// entire space is fully mapped. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_reset_address_trans() -> () { + crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** High-level Flash Commands **************** + +declare_rom_function! { + /// Applies the address translation currently configured by QMI address + /// translation registers, ATRANS0 through ATRANS7. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Translating an address outside of the XIP runtime address window, or + /// beyond the bounds of an ATRANSx_SIZE field, returns + /// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage + /// address. Otherwise, return the storage address which QMI would access + /// when presented with the runtime address addr. This is effectively a + /// virtual-to-physical address translation for QMI. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_runtime_to_storage_addr()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Perform a flash read, erase, or program operation. + /// + /// Erase operations must be sector-aligned (4096 bytes) and sector- + /// multiple-sized, and program operations must be page-aligned (256 bytes) + /// and page-multiple-sized; misaligned erase and program operations will + /// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program + /// — is selected by the CFLASH_OP_BITS bitfield of the flags argument. + /// + /// See datasheet section 5.5.8.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_op()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Security Related Functions **************** + +declare_rom_function! { + /// Allow or disallow the specific NS API (note all NS APIs default to + /// disabled). + /// + /// See datasheet section 5.5.9.1 for more details. + /// + /// Supported architectures: ARM-S + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC) + } +} + +declare_rom_function! { + /// Utility method that can be used by secure ARM code to validate a buffer + /// passed to it from Non-secure code. + /// + /// See datasheet section 5.5.9.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn validate_ns_buffer() -> () { + crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Miscellaneous Functions **************** + +declare_rom_function! { + /// Resets the RP2350 and uses the watchdog facility to restart. + /// + /// See datasheet section 5.5.10.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [reboot()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Resets internal bootrom state. + /// + /// See datasheet section 5.5.10.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn bootrom_state_reset(flags: u32) -> () { + crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Set a boot ROM callback. + /// + /// The only supported callback_number is 0 which sets the callback used for + /// the secure_call API. + /// + /// See datasheet section 5.5.10.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 { + crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** System Information Functions **************** + +declare_rom_function! { + /// Fills a buffer with various system information. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_sys_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Fills a buffer with information from the partition table. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_partition_table_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Loads the current partition table from flash, if present. + /// + /// See datasheet section 5.5.11.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 { + crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Writes data from a buffer into OTP, or reads data from OTP into a buffer. + /// + /// See datasheet section 5.5.11.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [otp_access()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Boot Related Functions **************** + +declare_rom_function! { + /// Determines which of the partitions has the "better" IMAGE_DEF. In the + /// case of executable images, this is the one that would be booted. + /// + /// See datasheet section 5.5.12.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Searches a memory region for a launchable image, and executes it if + /// possible. + /// + /// See datasheet section 5.5.12.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Perform an "explicit" buy of an executable launched via an IMAGE_DEF + /// which was "explicit buy" flagged. + /// + /// See datasheet section 5.5.12.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Not yet documented. + /// + /// See datasheet section 5.5.12.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Returns: The index of the B partition of partition A if a partition + /// table is present and loaded, and there is a partition A with a B + /// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND. + /// + /// See datasheet section 5.5.12.5 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_b_partition(partition_a: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Non-secure-specific Functions **************** + +// NB: The "secure_call" function should be here, but it doesn't have a fixed +// function signature as it is designed to let you bounce into any secure +// function from non-secure mode. + +// **************** RISC-V Functions **************** + +declare_rom_function! { + /// Set stack for RISC-V bootrom functions to use. + /// + /// See datasheet section 5.5.14.1 for more details. + /// + /// Supported architectures: RISC-V + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::inner::rt_flags::FUNC_RISCV) + } +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32; + unsafe { ptr.read() } +} + +/// A pointer to the resident partition table info. +/// +/// The resident partition table is the subset of the full partition table that +/// is kept in memory, and used for flash permissions. +pub fn partition_table_pointer() -> *const u32 { + let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32; + unsafe { ptr.read() } +} + +/// Determine if we are in secure mode +/// +/// Returns `true` if we are in secure mode and `false` if we are in non-secure +/// mode. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub fn is_secure_mode() -> bool { + // Look at the start of ROM, which is always readable + #[allow(clippy::zero_ptr)] + let rom_base: *mut u32 = 0x0000_0000 as *mut u32; + // Use the 'tt' instruction to check the permissions for that address + let tt = cortex_m::asm::tt(rom_base); + // Is the secure bit set? => secure mode + (tt & (1 << 22)) != 0 +} + +/// Determine if we are in secure mode +/// +/// Always returns `false` on RISC-V as it is impossible to determine if +/// you are in Machine Mode or User Mode by design. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub fn is_secure_mode() -> bool { + false +} diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs index 2ce7ac645..63cf91d28 100644 --- a/embassy-rp/src/rtc/mod.rs +++ b/embassy-rp/src/rtc/mod.rs @@ -1,7 +1,7 @@ //! RTC driver. mod filter; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; pub use self::filter::DateTimeFilter; @@ -14,7 +14,7 @@ use crate::clocks::clk_rtc_freq; /// A reference to the real time clock of the system pub struct Rtc<'d, T: Instance> { - inner: PeripheralRef<'d, T>, + inner: Peri<'d, T>, } impl<'d, T: Instance> Rtc<'d, T> { @@ -23,9 +23,7 @@ impl<'d, T: Instance> Rtc<'d, T> { /// # Errors /// /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. - pub fn new(inner: impl Peripheral

+ 'd) -> Self { - into_ref!(inner); - + pub fn new(inner: Peri<'d, T>) -> Self { // Set the RTC divider inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)); @@ -194,7 +192,7 @@ trait SealedInstance { /// RTC peripheral instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance {} +pub trait Instance: SealedInstance + PeripheralType {} impl SealedInstance for crate::peripherals::RTC { fn regs(&self) -> crate::pac::rtc::Rtc { diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index b89df74a2..559b3b909 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -3,12 +3,12 @@ use core::marker::PhantomData; use embassy_embedded_hal::SetConfig; use embassy_futures::join::join; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; pub use embedded_hal_02::spi::{Phase, Polarity}; use crate::dma::{AnyChannel, Channel}; use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin as _}; -use crate::{pac, peripherals, Peripheral}; +use crate::{pac, peripherals}; /// SPI errors. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -42,9 +42,9 @@ impl Default for Config { /// SPI driver. pub struct Spi<'d, T: Instance, M: Mode> { - inner: PeripheralRef<'d, T>, - tx_dma: Option>, - rx_dma: Option>, + inner: Peri<'d, T>, + tx_dma: Option>, + rx_dma: Option>, phantom: PhantomData<(&'d mut T, M)>, } @@ -73,27 +73,18 @@ fn calc_prescs(freq: u32) -> (u8, u8) { impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { fn new_inner( - inner: impl Peripheral

+ 'd, - clk: Option>, - mosi: Option>, - miso: Option>, - cs: Option>, - tx_dma: Option>, - rx_dma: Option>, + inner: Peri<'d, T>, + clk: Option>, + mosi: Option>, + miso: Option>, + cs: Option>, + tx_dma: Option>, + rx_dma: Option>, config: Config, ) -> Self { - into_ref!(inner); + Self::apply_config(&inner, &config); let p = inner.regs(); - let (presc, postdiv) = calc_prescs(config.frequency); - - p.cpsr().write(|w| w.set_cpsdvsr(presc)); - p.cr0().write(|w| { - w.set_dss(0b0111); // 8bit - w.set_spo(config.polarity == Polarity::IdleHigh); - w.set_sph(config.phase == Phase::CaptureOnSecondTransition); - w.set_scr(postdiv); - }); // Always enable DREQ signals -- harmless if DMA is not listening p.dmacr().write(|reg| { @@ -164,6 +155,23 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { } } + /// Private function to apply SPI configuration (phase, polarity, frequency) settings. + /// + /// Driver should be disabled before making changes and reenabled after the modifications + /// are applied. + fn apply_config(inner: &Peri<'d, T>, config: &Config) { + let p = inner.regs(); + let (presc, postdiv) = calc_prescs(config.frequency); + + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().write(|w| { + w.set_dss(0b0111); // 8bit + w.set_spo(config.polarity == Polarity::IdleHigh); + w.set_sph(config.phase == Phase::CaptureOnSecondTransition); + w.set_scr(postdiv); + }); + } + /// Write data to SPI blocking execution until done. pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { let p = self.inner.regs(); @@ -244,23 +252,36 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { // enable p.cr1().write(|w| w.set_sse(true)); } + + /// Set SPI config. + pub fn set_config(&mut self, config: &Config) { + let p = self.inner.regs(); + + // disable + p.cr1().write(|w| w.set_sse(false)); + + // change stuff + Self::apply_config(&self.inner, config); + + // enable + p.cr1().write(|w| w.set_sse(true)); + } } impl<'d, T: Instance> Spi<'d, T, Blocking> { /// Create an SPI driver in blocking mode. pub fn new_blocking( - inner: impl Peripheral

+ 'd, - clk: impl Peripheral

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

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

+ 'd> + 'd, + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, config: Config, ) -> Self { - into_ref!(clk, mosi, miso); Self::new_inner( inner, - Some(clk.map_into()), - Some(mosi.map_into()), - Some(miso.map_into()), + Some(clk.into()), + Some(mosi.into()), + Some(miso.into()), None, None, None, @@ -270,16 +291,15 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { /// Create an SPI driver in blocking mode supporting writes only. pub fn new_blocking_txonly( - inner: impl Peripheral

+ 'd, - clk: impl Peripheral

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

+ 'd> + 'd, + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, config: Config, ) -> Self { - into_ref!(clk, mosi); Self::new_inner( inner, - Some(clk.map_into()), - Some(mosi.map_into()), + Some(clk.into()), + Some(mosi.into()), None, None, None, @@ -290,17 +310,16 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { /// Create an SPI driver in blocking mode supporting reads only. pub fn new_blocking_rxonly( - inner: impl Peripheral

+ 'd, - clk: impl Peripheral

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

+ 'd> + 'd, + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, config: Config, ) -> Self { - into_ref!(clk, miso); Self::new_inner( inner, - Some(clk.map_into()), + Some(clk.into()), None, - Some(miso.map_into()), + Some(miso.into()), None, None, None, @@ -312,43 +331,41 @@ impl<'d, T: Instance> Spi<'d, T, Blocking> { impl<'d, T: Instance> Spi<'d, T, Async> { /// Create an SPI driver in async mode supporting DMA operations. pub fn new( - inner: impl Peripheral

+ 'd, - clk: impl Peripheral

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

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

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

+ 'd, - rx_dma: impl Peripheral

+ 'd, + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - into_ref!(tx_dma, rx_dma, clk, mosi, miso); Self::new_inner( inner, - Some(clk.map_into()), - Some(mosi.map_into()), - Some(miso.map_into()), + Some(clk.into()), + Some(mosi.into()), + Some(miso.into()), None, - Some(tx_dma.map_into()), - Some(rx_dma.map_into()), + Some(tx_dma.into()), + Some(rx_dma.into()), config, ) } /// Create an SPI driver in async mode supporting DMA write operations only. pub fn new_txonly( - inner: impl Peripheral

+ 'd, - clk: impl Peripheral

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

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

+ 'd, + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + tx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - into_ref!(tx_dma, clk, mosi); Self::new_inner( inner, - Some(clk.map_into()), - Some(mosi.map_into()), + Some(clk.into()), + Some(mosi.into()), None, None, - Some(tx_dma.map_into()), + Some(tx_dma.into()), None, config, ) @@ -356,28 +373,28 @@ impl<'d, T: Instance> Spi<'d, T, Async> { /// Create an SPI driver in async mode supporting DMA read operations only. pub fn new_rxonly( - inner: impl Peripheral

+ 'd, - clk: impl Peripheral

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

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

+ 'd, + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - into_ref!(rx_dma, clk, miso); Self::new_inner( inner, - Some(clk.map_into()), + Some(clk.into()), None, - Some(miso.map_into()), + Some(miso.into()), None, - None, - Some(rx_dma.map_into()), + Some(tx_dma.into()), + Some(rx_dma.into()), config, ) } /// Write data to SPI using DMA. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { - let tx_ch = self.tx_dma.as_mut().unwrap(); + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); let tx_transfer = unsafe { // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. @@ -402,14 +419,14 @@ impl<'d, T: Instance> Spi<'d, T, Async> { pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { // Start RX first. Transfer starts when TX starts, if RX // is not started yet we might lose bytes. - let rx_ch = self.rx_dma.as_mut().unwrap(); + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); let rx_transfer = unsafe { // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, buffer, T::RX_DREQ) }; - let tx_ch = self.tx_dma.as_mut().unwrap(); + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); let tx_transfer = unsafe { // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. @@ -437,20 +454,20 @@ impl<'d, T: Instance> Spi<'d, T, Async> { async fn transfer_inner(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { // Start RX first. Transfer starts when TX starts, if RX // is not started yet we might lose bytes. - let rx_ch = self.rx_dma.as_mut().unwrap(); + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); let rx_transfer = unsafe { // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, rx, T::RX_DREQ) }; - let mut tx_ch = self.tx_dma.as_mut().unwrap(); + let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. let tx_transfer = async { let p = self.inner.regs(); unsafe { - crate::dma::write(&mut tx_ch, tx, p.dr().as_ptr() as *mut _, T::TX_DREQ).await; + crate::dma::write(tx_ch.reborrow(), tx, p.dr().as_ptr() as *mut _, T::TX_DREQ).await; if rx.len() > tx.len() { let write_bytes_len = rx.len() - tx.len(); @@ -494,7 +511,7 @@ pub trait Mode: SealedMode {} /// SPI instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance {} +pub trait Instance: SealedInstance + PeripheralType {} macro_rules! impl_instance { ($type:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { @@ -696,15 +713,7 @@ impl<'d, T: Instance, M: Mode> SetConfig for Spi<'d, T, M> { type Config = Config; type ConfigError = (); fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { - let p = self.inner.regs(); - let (presc, postdiv) = calc_prescs(config.frequency); - p.cpsr().write(|w| w.set_cpsdvsr(presc)); - p.cr0().write(|w| { - w.set_dss(0b0111); // 8bit - w.set_spo(config.polarity == Polarity::IdleHigh); - w.set_sph(config.phase == Phase::CaptureOnSecondTransition); - w.set_scr(postdiv); - }); + self.set_config(config); Ok(()) } diff --git a/embassy-rp/src/spinlock.rs b/embassy-rp/src/spinlock.rs new file mode 100644 index 000000000..7effd2ae0 --- /dev/null +++ b/embassy-rp/src/spinlock.rs @@ -0,0 +1,75 @@ +use crate::pac; + +pub struct Spinlock(core::marker::PhantomData<()>) +where + Spinlock: SpinlockValid; + +impl Spinlock +where + Spinlock: SpinlockValid, +{ + /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is + /// already in use somewhere else. + pub fn try_claim() -> Option { + let lock = pac::SIO.spinlock(N).read(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + // Write (any value): release the lock + pac::SIO.spinlock(N).write_value(1); + } +} + +impl Drop for Spinlock +where + Spinlock: SpinlockValid, +{ + fn drop(&mut self) { + // This is safe because we own the object, and hence hold the lock. + unsafe { Self::release() } + } +} + +pub trait SpinlockValid {} +impl SpinlockValid for Spinlock<0> {} +impl SpinlockValid for Spinlock<1> {} +impl SpinlockValid for Spinlock<2> {} +impl SpinlockValid for Spinlock<3> {} +impl SpinlockValid for Spinlock<4> {} +impl SpinlockValid for Spinlock<5> {} +impl SpinlockValid for Spinlock<6> {} +impl SpinlockValid for Spinlock<7> {} +impl SpinlockValid for Spinlock<8> {} +impl SpinlockValid for Spinlock<9> {} +impl SpinlockValid for Spinlock<10> {} +impl SpinlockValid for Spinlock<11> {} +impl SpinlockValid for Spinlock<12> {} +impl SpinlockValid for Spinlock<13> {} +impl SpinlockValid for Spinlock<14> {} +impl SpinlockValid for Spinlock<15> {} +impl SpinlockValid for Spinlock<16> {} +impl SpinlockValid for Spinlock<17> {} +impl SpinlockValid for Spinlock<18> {} +impl SpinlockValid for Spinlock<19> {} +impl SpinlockValid for Spinlock<20> {} +impl SpinlockValid for Spinlock<21> {} +impl SpinlockValid for Spinlock<22> {} +impl SpinlockValid for Spinlock<23> {} +impl SpinlockValid for Spinlock<24> {} +impl SpinlockValid for Spinlock<25> {} +impl SpinlockValid for Spinlock<26> {} +impl SpinlockValid for Spinlock<27> {} +impl SpinlockValid for Spinlock<28> {} +impl SpinlockValid for Spinlock<29> {} +impl SpinlockValid for Spinlock<30> {} +impl SpinlockValid for Spinlock<31> {} diff --git a/embassy-rp/src/spinlock_mutex.rs b/embassy-rp/src/spinlock_mutex.rs new file mode 100644 index 000000000..85174cf86 --- /dev/null +++ b/embassy-rp/src/spinlock_mutex.rs @@ -0,0 +1,93 @@ +//! Mutex implementation utilizing an hardware spinlock + +use core::marker::PhantomData; +use core::sync::atomic::Ordering; + +use embassy_sync::blocking_mutex::raw::RawMutex; + +use crate::spinlock::{Spinlock, SpinlockValid}; + +/// A mutex that allows borrowing data across executors and interrupts by utilizing an hardware spinlock +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub struct SpinlockRawMutex { + _phantom: PhantomData<()>, +} +unsafe impl Send for SpinlockRawMutex {} +unsafe impl Sync for SpinlockRawMutex {} + +impl SpinlockRawMutex { + /// Create a new `SpinlockRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawMutex for SpinlockRawMutex +where + Spinlock: SpinlockValid, +{ + const INIT: Self = Self::new(); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + // Store the initial interrupt state in stack variable + let interrupts_active = cortex_m::register::primask::read().is_active(); + + // Spin until we get the lock + loop { + // Need to disable interrupts to ensure that we will not deadlock + // if an interrupt or higher prio locks the spinlock after we acquire the lock + cortex_m::interrupt::disable(); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + if let Some(lock) = Spinlock::::try_claim() { + // We just acquired the lock. + // 1. Forget it, so we don't immediately unlock + core::mem::forget(lock); + break; + } + // We didn't get the lock, enable interrupts if they were enabled before we started + if interrupts_active { + // safety: interrupts are only enabled, if they had been enabled before + unsafe { + cortex_m::interrupt::enable(); + } + } + } + + let retval = f(); + + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Release the spinlock to allow others to lock mutex again + // safety: this point is only reached a spinlock was acquired before + unsafe { + Spinlock::::release(); + } + + // Re-enable interrupts if they were enabled before the mutex was locked + if interrupts_active { + // safety: interrupts are only enabled, if they had been enabled before + unsafe { + cortex_m::interrupt::enable(); + } + } + + retval + } +} + +pub mod blocking_mutex { + //! Mutex implementation utilizing an hardware spinlock + use embassy_sync::blocking_mutex::Mutex; + + use crate::spinlock_mutex::SpinlockRawMutex; + /// A mutex that allows borrowing data across executors and interrupts by utilizing an hardware spinlock. + /// + /// # Safety + /// + /// This mutex is safe to share between different executors and interrupts. + pub type SpinlockMutex = Mutex, T>; +} diff --git a/embassy-rp/src/time_driver.rs b/embassy-rp/src/time_driver.rs index e5b407a29..d598287a9 100644 --- a/embassy-rp/src/time_driver.rs +++ b/embassy-rp/src/time_driver.rs @@ -1,11 +1,11 @@ //! Timer driver. -use core::cell::Cell; +use core::cell::{Cell, RefCell}; -use atomic_polyfill::{AtomicU8, Ordering}; use critical_section::CriticalSection; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::Mutex; -use embassy_time_driver::{AlarmHandle, Driver}; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; #[cfg(feature = "rp2040")] use pac::TIMER; #[cfg(feature = "_rp235x")] @@ -16,24 +16,19 @@ use crate::{interrupt, pac}; struct AlarmState { timestamp: Cell, - callback: Cell>, } unsafe impl Send for AlarmState {} -const ALARM_COUNT: usize = 4; -const DUMMY_ALARM: AlarmState = AlarmState { - timestamp: Cell::new(0), - callback: Cell::new(None), -}; - struct TimerDriver { - alarms: Mutex, - next_alarm: AtomicU8, + alarms: Mutex, + queue: Mutex>, } embassy_time_driver::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{ - alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [DUMMY_ALARM; ALARM_COUNT]), - next_alarm: AtomicU8::new(0), + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState { + timestamp: Cell::new(0), + }), + queue: Mutex::new(RefCell::new(Queue::new())) }); impl Driver for TimerDriver { @@ -48,85 +43,68 @@ impl Driver for TimerDriver { } } - unsafe fn allocate_alarm(&self) -> Option { - let id = self.next_alarm.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) - } else { - None - } - }); - - match id { - Ok(id) => Some(AlarmHandle::new(id)), - Err(_) => None, - } - } - - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - let n = alarm.id() as usize; + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { critical_section::with(|cs| { - let alarm = &self.alarms.borrow(cs)[n]; - alarm.callback.set(Some((callback, ctx))); - }) - } + let mut queue = self.queue.borrow(cs).borrow_mut(); - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { - let n = alarm.id() as usize; - critical_section::with(|cs| { - let alarm = &self.alarms.borrow(cs)[n]; - alarm.timestamp.set(timestamp); - - // Arm it. - // Note that we're not checking the high bits at all. This means the irq may fire early - // if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire - // it is checked if the alarm time has passed. - TIMER.alarm(n).write_value(timestamp as u32); - - let now = self.now(); - if timestamp <= now { - // If alarm timestamp has passed the alarm will not fire. - // Disarm the alarm and return `false` to indicate that. - TIMER.armed().write(|w| w.set_armed(1 << n)); - - alarm.timestamp.set(u64::MAX); - - false - } else { - true + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } } }) } } impl TimerDriver { - fn check_alarm(&self, n: usize) { + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let n = 0; + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + // Arm it. + // Note that we're not checking the high bits at all. This means the irq may fire early + // if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire + // it is checked if the alarm time has passed. + TIMER.alarm(n).write_value(timestamp as u32); + + let now = self.now(); + if timestamp <= now { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + TIMER.armed().write(|w| w.set_armed(1 << n)); + + alarm.timestamp.set(u64::MAX); + + false + } else { + true + } + } + + fn check_alarm(&self) { + let n = 0; critical_section::with(|cs| { - let alarm = &self.alarms.borrow(cs)[n]; + // clear the irq + TIMER.intr().write(|w| w.set_alarm(n, true)); + + let alarm = &self.alarms.borrow(cs); let timestamp = alarm.timestamp.get(); if timestamp <= self.now() { - self.trigger_alarm(n, cs) + self.trigger_alarm(cs) } else { // Not elapsed, arm it again. // This can happen if it was set more than 2^32 us in the future. TIMER.alarm(n).write_value(timestamp as u32); } }); - - // clear the irq - TIMER.intr().write(|w| w.set_alarm(n, true)); } - fn trigger_alarm(&self, n: usize, cs: CriticalSection) { - // disarm - TIMER.armed().write(|w| w.set_armed(1 << n)); - - let alarm = &self.alarms.borrow(cs)[n]; - alarm.timestamp.set(u64::MAX); - - // Call after clearing alarm, so the callback can set another alarm. - if let Some((f, ctx)) = alarm.callback.get() { - f(ctx); + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); } } } @@ -135,79 +113,32 @@ impl TimerDriver { pub unsafe fn init() { // init alarms critical_section::with(|cs| { - let alarms = DRIVER.alarms.borrow(cs); - for a in alarms { - a.timestamp.set(u64::MAX); - } + let alarm = DRIVER.alarms.borrow(cs); + alarm.timestamp.set(u64::MAX); }); - // enable all irqs + // enable irq TIMER.inte().write(|w| { w.set_alarm(0, true); - w.set_alarm(1, true); - w.set_alarm(2, true); - w.set_alarm(3, true); }); #[cfg(feature = "rp2040")] { interrupt::TIMER_IRQ_0.enable(); - interrupt::TIMER_IRQ_1.enable(); - interrupt::TIMER_IRQ_2.enable(); - interrupt::TIMER_IRQ_3.enable(); } #[cfg(feature = "_rp235x")] { interrupt::TIMER0_IRQ_0.enable(); - interrupt::TIMER0_IRQ_1.enable(); - interrupt::TIMER0_IRQ_2.enable(); - interrupt::TIMER0_IRQ_3.enable(); } } #[cfg(all(feature = "rt", feature = "rp2040"))] #[interrupt] fn TIMER_IRQ_0() { - DRIVER.check_alarm(0) -} - -#[cfg(all(feature = "rt", feature = "rp2040"))] -#[interrupt] -fn TIMER_IRQ_1() { - DRIVER.check_alarm(1) -} - -#[cfg(all(feature = "rt", feature = "rp2040"))] -#[interrupt] -fn TIMER_IRQ_2() { - DRIVER.check_alarm(2) -} - -#[cfg(all(feature = "rt", feature = "rp2040"))] -#[interrupt] -fn TIMER_IRQ_3() { - DRIVER.check_alarm(3) + DRIVER.check_alarm() } #[cfg(all(feature = "rt", feature = "_rp235x"))] #[interrupt] fn TIMER0_IRQ_0() { - DRIVER.check_alarm(0) -} - -#[cfg(all(feature = "rt", feature = "_rp235x"))] -#[interrupt] -fn TIMER0_IRQ_1() { - DRIVER.check_alarm(1) -} - -#[cfg(all(feature = "rt", feature = "_rp235x"))] -#[interrupt] -fn TIMER0_IRQ_2() { - DRIVER.check_alarm(2) -} - -#[cfg(all(feature = "rt", feature = "_rp235x"))] -#[interrupt] -fn TIMER0_IRQ_3() { - DRIVER.check_alarm(3) + DRIVER.check_alarm() } diff --git a/embassy-rp/src/trng.rs b/embassy-rp/src/trng.rs new file mode 100644 index 000000000..a3f23c1f2 --- /dev/null +++ b/embassy-rp/src/trng.rs @@ -0,0 +1,449 @@ +//! True Random Number Generator (TRNG) driver. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::Not; +use core::task::Poll; + +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::peripherals::TRNG; +use crate::{interrupt, pac}; + +trait SealedInstance { + fn regs() -> pac::trng::Trng; + fn waker() -> &'static AtomicWaker; +} + +/// TRNG peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +impl SealedInstance for TRNG { + fn regs() -> rp_pac::trng::Trng { + pac::TRNG + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } +} + +impl Instance for TRNG { + type Interrupt = interrupt::typelevel::TRNG_IRQ; +} + +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +/// TRNG ROSC Inverter chain length options. +pub enum InverterChainLength { + None = 0, + One, + Two, + Three, + Four, +} + +impl From for u8 { + fn from(value: InverterChainLength) -> Self { + value as u8 + } +} + +/// Configuration for the TRNG. +/// +/// - Three built in entropy checks +/// - ROSC frequency controlled by selecting one of ROSC chain lengths +/// - Sample period in terms of system clock ticks +/// +/// +/// Default configuration is based on the following from documentation: +/// +/// ---- +/// +/// RP2350 Datasheet 12.12.2 +/// +/// ... +/// +/// When configuring the TRNG block, consider the following principles: +/// • As average generation time increases, result quality increases and failed entropy checks decrease. +/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and +/// failed entropy checks. +/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or +/// 1 and sample count settings of 20-25. +/// Larger sample count settings (e.g. 100) provide proportionately slower average generation times. These settings +/// significantly reduce, but do not eliminate NIST test failures and entropy check failures. Results occasionally take an +/// especially long time to generate. +/// +/// --- +/// +/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly +/// by setting the sample period to 0. Random data collected this way is then passed through +/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!). +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Bypass TRNG autocorrelation test + pub disable_autocorrelation_test: bool, + /// Bypass CRNGT test + pub disable_crngt_test: bool, + /// When set, the Von-Neuman balancer is bypassed (including the + /// 32 consecutive bits test) + pub disable_von_neumann_balancer: bool, + /// Sets the number of rng_clk cycles between two consecutive + /// ring oscillator samples. + /// Note: If the von Neumann decorrelator is bypassed, the minimum value for + /// sample counter must not be less than seventeen + pub sample_count: u32, + /// Selects the number of inverters (out of four possible + /// selections) in the ring oscillator (the entropy source). Higher values select + /// longer inverter chain lengths. + pub inverter_chain_length: InverterChainLength, +} + +impl Default for Config { + fn default() -> Self { + Config { + // WARNING: Disabling these tests increases likelihood of poor rng results. + disable_autocorrelation_test: false, + disable_crngt_test: false, + disable_von_neumann_balancer: false, + sample_count: 25, + inverter_chain_length: InverterChainLength::One, + } + } +} + +/// True Random Number Generator Driver for RP2350 +/// +/// This driver provides async and blocking options. +/// +/// See [Config] for configuration details. +/// +/// Usage example: +/// ```no_run +/// use embassy_executor::Spawner; +/// use embassy_rp::trng::Trng; +/// use embassy_rp::peripherals::TRNG; +/// use embassy_rp::bind_interrupts; +/// +/// bind_interrupts!(struct Irqs { +/// TRNG_IRQ => embassy_rp::trng::InterruptHandler; +/// }); +/// +/// #[embassy_executor::main] +/// async fn main(spawner: Spawner) { +/// let peripherals = embassy_rp::init(Default::default()); +/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); +/// +/// let mut randomness = [0u8; 58]; +/// loop { +/// trng.fill_bytes(&mut randomness).await; +/// assert_ne!(randomness, [0u8; 58]); +/// } +///} +/// ``` +pub struct Trng<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + config: Config, +} + +/// 12.12.1. Overview +/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of +/// periodic samples from the TRNG block’s internal Ring Oscillator (ROSC). +const TRNG_BLOCK_SIZE_BITS: usize = 192; +const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8; + +impl<'d, T: Instance> Trng<'d, T> { + /// Create a new TRNG driver. + pub fn new(_trng: Peri<'d, T>, _irq: impl Binding> + 'd, config: Config) -> Self { + let trng = Trng { + phantom: PhantomData, + config: config, + }; + trng.initialize_rng(); + trng + } + + fn start_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + // Enable TRNG ROSC + source_enable_register.write(|w| w.set_rnd_src_en(true)); + } + + fn stop_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + source_enable_register.write(|w| w.set_rnd_src_en(false)); + let reset_bits_counter_register = regs.rst_bits_counter(); + reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true)); + } + + fn initialize_rng(&self) { + let regs = T::regs(); + + regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false)); + + let trng_config_register = regs.trng_config(); + trng_config_register.write(|w| { + w.set_rnd_src_sel(self.config.inverter_chain_length.clone().into()); + }); + + let sample_count_register = regs.sample_cnt1(); + sample_count_register.write(|w| { + *w = self.config.sample_count; + }); + + let debug_control_register = regs.trng_debug_control(); + debug_control_register.write(|w| { + w.set_auto_correlate_bypass(self.config.disable_autocorrelation_test); + w.set_trng_crngt_bypass(self.config.disable_crngt_test); + w.set_vnc_bypass(self.config.disable_von_neumann_balancer); + }); + } + + fn enable_irq(&self) { + unsafe { T::Interrupt::enable() } + } + + fn disable_irq(&self) { + T::Interrupt::disable(); + } + + fn blocking_wait_for_successful_generation(&self) { + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let mut success = false; + while success.not() { + while trng_busy_register.read().trng_busy() {} + if trng_valid_register.read().ehr_valid().not() { + if regs.rng_isr().read().autocorr_err() { + regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true)); + // Fixed delay is required after TRNG soft reset. This read is sufficient. + regs.trng_sw_reset().read(); + self.initialize_rng(); + self.start_rng(); + } else { + panic!("RNG not busy, but ehr is not valid!") + } + } else { + success = true + } + } + } + + fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + let regs = T::regs(); + let ehr_data_regs = [ + regs.ehr_data0(), + regs.ehr_data1(), + regs.ehr_data2(), + regs.ehr_data3(), + regs.ehr_data4(), + regs.ehr_data5(), + ]; + + for (i, reg) in ehr_data_regs.iter().enumerate() { + buffer[i * 4..i * 4 + 4].copy_from_slice(®.read().to_ne_bytes()); + } + } + + fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + self.blocking_wait_for_successful_generation(); + self.read_ehr_registers_into_array(buffer); + } + + /// Fill the buffer with random bytes, async version. + pub async fn fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + + self.start_rng(); + self.enable_irq(); + + let mut bytes_transferred = 0usize; + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let waker = T::waker(); + + let destination_length = destination.len(); + + poll_fn(|context| { + waker.register(context.waker()); + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + if trng_busy_register.read().trng_busy() { + Poll::Pending + } else { + // If woken up and EHR is *not* valid, assume the trng has been reset and reinitialize, restart. + if trng_valid_register.read().ehr_valid().not() { + self.initialize_rng(); + self.start_rng(); + return Poll::Pending; + } + self.read_ehr_registers_into_array(&mut buffer); + let remaining = destination_length - bytes_transferred; + if remaining > TRNG_BLOCK_SIZE_BYTES { + destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES] + .copy_from_slice(&buffer); + bytes_transferred += TRNG_BLOCK_SIZE_BYTES + } else { + destination[bytes_transferred..bytes_transferred + remaining] + .copy_from_slice(&buffer[0..remaining]); + bytes_transferred += remaining + } + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + }) + .await + } + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + self.start_rng(); + + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) { + self.blocking_wait_for_successful_generation(); + self.blocking_read_ehr_registers_into_array(&mut buffer); + chunk.copy_from_slice(&buffer[..chunk.len()]) + } + self.stop_rng() + } + + /// Return a random u32, blocking. + pub fn blocking_next_u32(&mut self) -> u32 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = regs.ehr_data5().read(); + self.stop_rng(); + result + } + + /// Return a random u64, blocking. + pub fn blocking_next_u64(&mut self) -> u64 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + + let low = regs.ehr_data4().read() as u64; + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = (regs.ehr_data5().read() as u64) << 32 | low; + self.stop_rng(); + result + } +} + +impl<'d, T: Instance> rand_core_06::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} + +impl<'d, T: Instance> rand_core_06::CryptoRng for Trng<'d, T> {} + +impl<'d, T: Instance> rand_core_09::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } +} + +impl<'d, T: Instance> rand_core_09::CryptoRng for Trng<'d, T> {} + +/// TRNG interrupt handler. +pub struct InterruptHandler { + _trng: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let isr = regs.rng_isr().read(); + if isr.ehr_valid() { + regs.rng_icr().write(|w| { + w.set_ehr_valid(true); + }); + T::waker().wake(); + } else if isr.crngt_err() { + warn!("TRNG CRNGT error! Increase sample count to reduce likelihood"); + regs.rng_icr().write(|w| { + w.set_crngt_err(true); + }); + } else if isr.vn_err() { + warn!("TRNG Von-Neumann balancer error! Increase sample count to reduce likelihood"); + regs.rng_icr().write(|w| { + w.set_vn_err(true); + }); + } else if isr.autocorr_err() { + // 12.12.5. List of Registers + // ... + // TRNG: RNG_ISR Register + // ... + // AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row. + // When set, RNG ceases functioning until next reset + warn!("TRNG Autocorrect error! Resetting TRNG. Increase sample count to reduce likelihood"); + regs.trng_sw_reset().write(|w| { + w.set_trng_sw_reset(true); + }); + // Fixed delay is required after TRNG soft reset, this read is sufficient. + regs.trng_sw_reset().read(); + // Wake up to reinitialize and restart the TRNG. + T::waker().wake(); + } + } +} diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs index 152a432c9..02649ad81 100644 --- a/embassy-rp/src/uart/buffered.rs +++ b/embassy-rp/src/uart/buffered.rs @@ -34,28 +34,29 @@ impl State { } /// Buffered UART driver. -pub struct BufferedUart<'d, T: Instance> { - pub(crate) rx: BufferedUartRx<'d, T>, - pub(crate) tx: BufferedUartTx<'d, T>, +pub struct BufferedUart { + pub(super) rx: BufferedUartRx, + pub(super) tx: BufferedUartTx, } /// Buffered UART RX handle. -pub struct BufferedUartRx<'d, T: Instance> { - pub(crate) phantom: PhantomData<&'d mut T>, +pub struct BufferedUartRx { + pub(super) info: &'static Info, + pub(super) state: &'static State, } /// Buffered UART TX handle. -pub struct BufferedUartTx<'d, T: Instance> { - pub(crate) phantom: PhantomData<&'d mut T>, +pub struct BufferedUartTx { + pub(super) info: &'static Info, + pub(super) state: &'static State, } -pub(crate) fn init_buffers<'d, T: Instance + 'd>( - _irq: impl Binding>, +pub(super) fn init_buffers<'d>( + info: &Info, + state: &State, tx_buffer: Option<&'d mut [u8]>, rx_buffer: Option<&'d mut [u8]>, ) { - let state = T::buffered_state(); - if let Some(tx_buffer) = tx_buffer { let len = tx_buffer.len(); unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; @@ -76,65 +77,73 @@ pub(crate) fn init_buffers<'d, T: Instance + 'd>( // This means we can leave the interrupt enabled the whole time as long as // we clear it after it happens. The downside is that the we manually have // to pend the ISR when we want data transmission to start. - let regs = T::regs(); - regs.uartimsc().write(|w| { + info.regs.uartimsc().write(|w| { w.set_rxim(true); w.set_rtim(true); w.set_txim(true); }); - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; } -impl<'d, T: Instance> BufferedUart<'d, T> { +impl BufferedUart { /// Create a buffered UART instance. - pub fn new( - _uart: impl Peripheral

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

> + 'd, - rx: impl Peripheral

> + 'd, + pub fn new<'d, T: Instance>( + _uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(tx, rx); - - super::Uart::<'d, T, Async>::init(Some(tx.map_into()), Some(rx.map_into()), None, None, config); - init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), Some(rx.into()), None, None, config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); Self { - rx: BufferedUartRx { phantom: PhantomData }, - tx: BufferedUartTx { phantom: PhantomData }, + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, } } /// Create a buffered UART instance with flow control. - pub fn new_with_rtscts( - _uart: impl Peripheral

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

> + 'd, - rx: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, + pub fn new_with_rtscts<'d, T: Instance>( + _uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(tx, rx, cts, rts); - - super::Uart::<'d, T, Async>::init( - Some(tx.map_into()), - Some(rx.map_into()), - Some(rts.map_into()), - Some(cts.map_into()), + super::Uart::<'d, Async>::init( + T::info(), + Some(tx.into()), + Some(rx.into()), + Some(rts.into()), + Some(cts.into()), config, ); - init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); Self { - rx: BufferedUartRx { phantom: PhantomData }, - tx: BufferedUartTx { phantom: PhantomData }, + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, } } @@ -164,72 +173,75 @@ impl<'d, T: Instance> BufferedUart<'d, T> { } /// sets baudrate on runtime - pub fn set_baudrate(&mut self, baudrate: u32) { - super::Uart::<'d, T, Async>::set_baudrate_inner(baudrate); + pub fn set_baudrate<'d>(&mut self, baudrate: u32) { + super::Uart::<'d, Async>::set_baudrate_inner(self.rx.info, baudrate); } /// Split into separate RX and TX handles. - pub fn split(self) -> (BufferedUartTx<'d, T>, BufferedUartRx<'d, T>) { + pub fn split(self) -> (BufferedUartTx, BufferedUartRx) { (self.tx, self.rx) } /// Split the Uart into a transmitter and receiver by mutable reference, /// which is particularly useful when having two tasks correlating to /// transmitting and receiving. - pub fn split_ref(&mut self) -> (&mut BufferedUartTx<'d, T>, &mut BufferedUartRx<'d, T>) { + pub fn split_ref(&mut self) -> (&mut BufferedUartTx, &mut BufferedUartRx) { (&mut self.tx, &mut self.rx) } } -impl<'d, T: Instance> BufferedUartRx<'d, T> { +impl BufferedUartRx { /// Create a new buffered UART RX. - pub fn new( - _uart: impl Peripheral

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

> + 'd, + pub fn new<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + rx: Peri<'d, impl RxPin>, rx_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(rx); + super::Uart::<'d, Async>::init(T::info(), None, Some(rx.into()), None, None, config); + init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); - super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), None, None, config); - init_buffers::(irq, None, Some(rx_buffer)); - - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } /// Create a new buffered UART RX with flow control. - pub fn new_with_rts( - _uart: impl Peripheral

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

> + 'd, - rts: impl Peripheral

> + 'd, + pub fn new_with_rts<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, rx_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(rx, rts); + super::Uart::<'d, Async>::init(T::info(), None, Some(rx.into()), Some(rts.into()), None, config); + init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); - super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), Some(rts.map_into()), None, config); - init_buffers::(irq, None, Some(rx_buffer)); - - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } - fn read<'a>(buf: &'a mut [u8]) -> impl Future> + 'a - where - T: 'd, - { + fn read<'a>( + info: &'static Info, + state: &'static State, + buf: &'a mut [u8], + ) -> impl Future> + 'a { poll_fn(move |cx| { - if let Poll::Ready(r) = Self::try_read(buf) { + if let Poll::Ready(r) = Self::try_read(info, state, buf) { return Poll::Ready(r); } - T::buffered_state().rx_waker.register(cx.waker()); + state.rx_waker.register(cx.waker()); Poll::Pending }) } - fn get_rx_error() -> Option { - let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed); + fn get_rx_error(state: &State) -> Option { + let errs = state.rx_error.swap(0, Ordering::Relaxed); if errs & RXE_OVERRUN != 0 { Some(Error::Overrun) } else if errs & RXE_BREAK != 0 { @@ -243,15 +255,11 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { } } - fn try_read(buf: &mut [u8]) -> Poll> - where - T: 'd, - { + fn try_read(info: &Info, state: &State, buf: &mut [u8]) -> Poll> { if buf.is_empty() { return Poll::Ready(Ok(0)); } - let state = T::buffered_state(); let mut rx_reader = unsafe { state.rx_buf.reader() }; let n = rx_reader.pop(|data| { let n = data.len().min(buf.len()); @@ -260,7 +268,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { }); let result = if n == 0 { - match Self::get_rx_error() { + match Self::get_rx_error(state) { None => return Poll::Pending, Some(e) => Err(e), } @@ -270,8 +278,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { // (Re-)Enable the interrupt to receive more data in case it was // disabled because the buffer was full or errors were detected. - let regs = T::regs(); - regs.uartimsc().write_set(|w| { + info.regs.uartimsc().write_set(|w| { w.set_rxim(true); w.set_rtim(true); }); @@ -282,23 +289,19 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { /// Read from UART RX buffer blocking execution until done. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result { loop { - match Self::try_read(buf) { + match Self::try_read(self.info, self.state, buf) { Poll::Ready(res) => return res, Poll::Pending => continue, } } } - fn fill_buf<'a>() -> impl Future> - where - T: 'd, - { + fn fill_buf<'a>(state: &'static State) -> impl Future> { poll_fn(move |cx| { - let state = T::buffered_state(); let mut rx_reader = unsafe { state.rx_buf.reader() }; let (p, n) = rx_reader.pop_buf(); let result = if n == 0 { - match Self::get_rx_error() { + match Self::get_rx_error(state) { None => { state.rx_waker.register(cx.waker()); return Poll::Pending; @@ -314,68 +317,70 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { }) } - fn consume(amt: usize) { - let state = T::buffered_state(); + fn consume(info: &Info, state: &State, amt: usize) { let mut rx_reader = unsafe { state.rx_buf.reader() }; rx_reader.pop_done(amt); // (Re-)Enable the interrupt to receive more data in case it was // disabled because the buffer was full or errors were detected. - let regs = T::regs(); - regs.uartimsc().write_set(|w| { + info.regs.uartimsc().write_set(|w| { w.set_rxim(true); w.set_rtim(true); }); } /// we are ready to read if there is data in the buffer - fn read_ready() -> Result { - let state = T::buffered_state(); + fn read_ready(state: &State) -> Result { Ok(!state.rx_buf.is_empty()) } } -impl<'d, T: Instance> BufferedUartTx<'d, T> { +impl BufferedUartTx { /// Create a new buffered UART TX. - pub fn new( - _uart: impl Peripheral

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

> + 'd, + pub fn new<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + tx: Peri<'d, impl TxPin>, tx_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(tx); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, None, config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, None, config); - init_buffers::(irq, Some(tx_buffer), None); - - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } /// Create a new buffered UART TX with flow control. - pub fn new_with_cts( - _uart: impl Peripheral

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

> + 'd, - cts: impl Peripheral

> + 'd, + pub fn new_with_cts<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + tx: Peri<'d, impl TxPin>, + cts: Peri<'d, impl CtsPin>, tx_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(tx, cts); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, Some(cts.into()), config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, Some(cts.map_into()), config); - init_buffers::(irq, Some(tx_buffer), None); - - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } - fn write(buf: &[u8]) -> impl Future> + '_ { + fn write<'d>( + info: &'static Info, + state: &'static State, + buf: &'d [u8], + ) -> impl Future> + 'd { poll_fn(move |cx| { if buf.is_empty() { return Poll::Ready(Ok(0)); } - let state = T::buffered_state(); let mut tx_writer = unsafe { state.tx_buf.writer() }; let n = tx_writer.push(|data| { let n = data.len().min(buf.len()); @@ -391,14 +396,13 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { // FIFO and the number of bytes drops below a threshold. When the // FIFO was empty we have to manually pend the interrupt to shovel // TX data from the buffer into the FIFO. - T::Interrupt::pend(); + info.interrupt.pend(); Poll::Ready(Ok(n)) }) } - fn flush() -> impl Future> { + fn flush(state: &'static State) -> impl Future> { poll_fn(move |cx| { - let state = T::buffered_state(); if !state.tx_buf.is_empty() { state.tx_waker.register(cx.waker()); return Poll::Pending; @@ -415,8 +419,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } loop { - let state = T::buffered_state(); - let mut tx_writer = unsafe { state.tx_buf.writer() }; + let mut tx_writer = unsafe { self.state.tx_buf.writer() }; let n = tx_writer.push(|data| { let n = data.len().min(buf.len()); data[..n].copy_from_slice(&buf[..n]); @@ -428,7 +431,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { // FIFO and the number of bytes drops below a threshold. When the // FIFO was empty we have to manually pend the interrupt to shovel // TX data from the buffer into the FIFO. - T::Interrupt::pend(); + self.info.interrupt.pend(); return Ok(n); } } @@ -437,8 +440,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { loop { - let state = T::buffered_state(); - if state.tx_buf.is_empty() { + if self.state.tx_buf.is_empty() { return Ok(()); } } @@ -446,7 +448,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// Check if UART is busy. pub fn busy(&self) -> bool { - T::regs().uartfr().read().busy() + self.info.regs.uartfr().read().busy() } /// Assert a break condition after waiting for the transmit buffers to empty, @@ -457,7 +459,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// This method may block for a long amount of time since it has to wait /// for the transmit fifo to empty, which may take a while on slow links. pub async fn send_break(&mut self, bits: u32) { - let regs = T::regs(); + let regs = self.info.regs; let bits = bits.max({ let lcr = regs.uartlcr_h().read(); let width = lcr.wlen() as u32 + 5; @@ -470,7 +472,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { let div_clk = clk_peri_freq() as u64 * 64; let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; - Self::flush().await.unwrap(); + Self::flush(self.state).await.unwrap(); while self.busy() {} regs.uartlcr_h().write_set(|w| w.set_brk(true)); Timer::after_micros(wait_usecs).await; @@ -478,28 +480,26 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> { +impl Drop for BufferedUartRx { fn drop(&mut self) { - let state = T::buffered_state(); - unsafe { state.rx_buf.deinit() } + unsafe { self.state.rx_buf.deinit() } // TX is inactive if the buffer is not available. // We can now unregister the interrupt handler - if !state.tx_buf.is_available() { - T::Interrupt::disable(); + if !self.state.tx_buf.is_available() { + self.info.interrupt.disable(); } } } -impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { +impl Drop for BufferedUartTx { fn drop(&mut self) { - let state = T::buffered_state(); - unsafe { state.tx_buf.deinit() } + unsafe { self.state.tx_buf.deinit() } // RX is inactive if the buffer is not available. // We can now unregister the interrupt handler - if !state.rx_buf.is_available() { - T::Interrupt::disable(); + if !self.state.rx_buf.is_available() { + self.info.interrupt.disable(); } } } @@ -511,7 +511,7 @@ pub struct BufferedInterruptHandler { impl interrupt::typelevel::Handler for BufferedInterruptHandler { unsafe fn on_interrupt() { - let r = T::regs(); + let r = T::info().regs; if r.uartdmacr().read().rxdmae() { return; } @@ -615,95 +615,95 @@ impl embedded_io::Error for Error { } } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUart<'d, T> { +impl embedded_io_async::ErrorType for BufferedUart { type Error = Error; } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartRx<'d, T> { +impl embedded_io_async::ErrorType for BufferedUartRx { type Error = Error; } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartTx<'d, T> { +impl embedded_io_async::ErrorType for BufferedUartTx { type Error = Error; } -impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUart<'d, T> { +impl embedded_io_async::Read for BufferedUart { async fn read(&mut self, buf: &mut [u8]) -> Result { - BufferedUartRx::<'d, T>::read(buf).await + BufferedUartRx::read(self.rx.info, self.rx.state, buf).await } } -impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> { +impl embedded_io_async::Read for BufferedUartRx { async fn read(&mut self, buf: &mut [u8]) -> Result { - Self::read(buf).await + Self::read(self.info, self.state, buf).await } } -impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> { +impl embedded_io_async::ReadReady for BufferedUart { fn read_ready(&mut self) -> Result { - BufferedUartRx::<'d, T>::read_ready() + BufferedUartRx::read_ready(self.rx.state) } } -impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> { +impl embedded_io_async::ReadReady for BufferedUartRx { fn read_ready(&mut self) -> Result { - Self::read_ready() + Self::read_ready(self.state) } } -impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> { +impl embedded_io_async::BufRead for BufferedUart { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - BufferedUartRx::<'d, T>::fill_buf().await + BufferedUartRx::fill_buf(self.rx.state).await } fn consume(&mut self, amt: usize) { - BufferedUartRx::<'d, T>::consume(amt) + BufferedUartRx::consume(self.rx.info, self.rx.state, amt) } } -impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUartRx<'d, T> { +impl embedded_io_async::BufRead for BufferedUartRx { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - Self::fill_buf().await + Self::fill_buf(self.state).await } fn consume(&mut self, amt: usize) { - Self::consume(amt) + Self::consume(self.info, self.state, amt) } } -impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUart<'d, T> { +impl embedded_io_async::Write for BufferedUart { async fn write(&mut self, buf: &[u8]) -> Result { - BufferedUartTx::<'d, T>::write(buf).await + BufferedUartTx::write(self.tx.info, self.tx.state, buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - BufferedUartTx::<'d, T>::flush().await + BufferedUartTx::flush(self.tx.state).await } } -impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUartTx<'d, T> { +impl embedded_io_async::Write for BufferedUartTx { async fn write(&mut self, buf: &[u8]) -> Result { - Self::write(buf).await + Self::write(self.info, self.state, buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - Self::flush().await + Self::flush(self.state).await } } -impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUart<'d, T> { +impl embedded_io::Read for BufferedUart { fn read(&mut self, buf: &mut [u8]) -> Result { self.rx.blocking_read(buf) } } -impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUartRx<'d, T> { +impl embedded_io::Read for BufferedUartRx { fn read(&mut self, buf: &mut [u8]) -> Result { self.blocking_read(buf) } } -impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { +impl embedded_io::Write for BufferedUart { fn write(&mut self, buf: &[u8]) -> Result { self.tx.blocking_write(buf) } @@ -713,7 +713,7 @@ impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { } } -impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { +impl embedded_io::Write for BufferedUartTx { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write(buf) } @@ -723,11 +723,11 @@ impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T> { +impl embedded_hal_02::serial::Read for BufferedUartRx { type Error = Error; fn read(&mut self) -> Result> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -748,7 +748,7 @@ impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T } } -impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d, T> { +impl embedded_hal_02::blocking::serial::Write for BufferedUartTx { type Error = Error; fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { @@ -767,7 +767,7 @@ impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedU } } -impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> { +impl embedded_hal_02::serial::Read for BufferedUart { type Error = Error; fn read(&mut self) -> Result> { @@ -775,7 +775,7 @@ impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> } } -impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUart<'d, T> { +impl embedded_hal_02::blocking::serial::Write for BufferedUart { type Error = Error; fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { @@ -794,25 +794,25 @@ impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedU } } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUartRx { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUartTx { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUart { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> { +impl embedded_hal_nb::serial::Read for BufferedUartRx { fn read(&mut self) -> nb::Result { embedded_hal_02::serial::Read::read(self) } } -impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { +impl embedded_hal_nb::serial::Write for BufferedUartTx { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) } @@ -822,13 +822,13 @@ impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::Read for BufferedUart { fn read(&mut self) -> Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } -impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::Write for BufferedUart { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) } diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index aba4b792a..c3a15fda5 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -5,7 +5,7 @@ use core::task::Poll; use atomic_polyfill::{AtomicU16, Ordering}; use embassy_futures::select::{select, Either}; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; use embassy_time::{Delay, Timer}; use pac::uart::regs::Uartris; @@ -13,9 +13,10 @@ use pac::uart::regs::Uartris; use crate::clocks::clk_peri_freq; use crate::dma::{AnyChannel, Channel}; use crate::gpio::{AnyPin, SealedPin}; -use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::interrupt::typelevel::{Binding, Interrupt as _}; +use crate::interrupt::{Interrupt, InterruptExt}; use crate::pac::io::vals::{Inover, Outover}; -use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; +use crate::{interrupt, pac, peripherals, RegExt}; mod buffered; pub use buffered::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, BufferedUartTx}; @@ -135,38 +136,41 @@ pub struct DmaState { } /// UART driver. -pub struct Uart<'d, T: Instance, M: Mode> { - tx: UartTx<'d, T, M>, - rx: UartRx<'d, T, M>, +pub struct Uart<'d, M: Mode> { + tx: UartTx<'d, M>, + rx: UartRx<'d, M>, } /// UART TX driver. -pub struct UartTx<'d, T: Instance, M: Mode> { - tx_dma: Option>, - phantom: PhantomData<(&'d mut T, M)>, +pub struct UartTx<'d, M: Mode> { + info: &'static Info, + tx_dma: Option>, + phantom: PhantomData, } /// UART RX driver. -pub struct UartRx<'d, T: Instance, M: Mode> { - rx_dma: Option>, - phantom: PhantomData<(&'d mut T, M)>, +pub struct UartRx<'d, M: Mode> { + info: &'static Info, + dma_state: &'static DmaState, + rx_dma: Option>, + phantom: PhantomData, } -impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { +impl<'d, M: Mode> UartTx<'d, M> { /// Create a new DMA-enabled UART which can only send data - pub fn new( - _uart: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

+ 'd, + pub fn new( + _uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + tx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - into_ref!(tx, tx_dma); - Uart::::init(Some(tx.map_into()), None, None, None, config); - Self::new_inner(Some(tx_dma.map_into())) + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner(T::info(), Some(tx_dma.into())) } - fn new_inner(tx_dma: Option>) -> Self { + fn new_inner(info: &'static Info, tx_dma: Option>) -> Self { Self { + info, tx_dma, phantom: PhantomData, } @@ -174,7 +178,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - let r = T::regs(); + let r = self.info.regs; for &b in buffer { while r.uartfr().read().txff() {} r.uartdr().write(|w| w.set_data(b)); @@ -184,14 +188,13 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { - let r = T::regs(); - while !r.uartfr().read().txfe() {} + while !self.info.regs.uartfr().read().txfe() {} Ok(()) } /// Check if UART is busy transmitting. pub fn busy(&self) -> bool { - T::regs().uartfr().read().busy() + self.info.regs.uartfr().read().busy() } /// Assert a break condition after waiting for the transmit buffers to empty, @@ -202,7 +205,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// This method may block for a long amount of time since it has to wait /// for the transmit fifo to empty, which may take a while on slow links. pub async fn send_break(&mut self, bits: u32) { - let regs = T::regs(); + let regs = self.info.regs; let bits = bits.max({ let lcr = regs.uartlcr_h().read(); let width = lcr.wlen() as u32 + 5; @@ -223,60 +226,80 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { } } -impl<'d, T: Instance> UartTx<'d, T, Blocking> { +impl<'d> UartTx<'d, Blocking> { + /// Create a new UART TX instance for blocking mode operations. + pub fn new_blocking(_uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner(T::info(), None) + } + /// Convert this uart TX instance into a buffered uart using the provided /// irq and transmit buffer. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], - ) -> BufferedUartTx<'d, T> { - buffered::init_buffers::(irq, Some(tx_buffer), None); + ) -> BufferedUartTx { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - BufferedUartTx { phantom: PhantomData } + BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + } } } -impl<'d, T: Instance> UartTx<'d, T, Async> { +impl<'d> UartTx<'d, Async> { /// Write to UART TX from the provided buffer using DMA. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { - let ch = self.tx_dma.as_mut().unwrap(); + let ch = self.tx_dma.as_mut().unwrap().reborrow(); let transfer = unsafe { - T::regs().uartdmacr().write_set(|reg| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_txdmae(true); }); // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ.into()) + crate::dma::write( + ch, + buffer, + self.info.regs.uartdr().as_ptr() as *mut _, + self.info.tx_dreq.into(), + ) }; transfer.await; Ok(()) } } -impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { +impl<'d, M: Mode> UartRx<'d, M> { /// Create a new DMA-enabled UART which can only receive data - pub fn new( - _uart: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, + pub fn new( + _uart: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, _irq: impl Binding>, - rx_dma: impl Peripheral

+ 'd, + rx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - into_ref!(rx, rx_dma); - Uart::::init(None, Some(rx.map_into()), None, None, config); - Self::new_inner(true, Some(rx_dma.map_into())) + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner(T::info(), T::dma_state(), true, Some(rx_dma.into())) } - fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + fn new_inner( + info: &'static Info, + dma_state: &'static DmaState, + has_irq: bool, + rx_dma: Option>, + ) -> Self { debug_assert_eq!(has_irq, rx_dma.is_some()); if has_irq { // disable all error interrupts initially - T::regs().uartimsc().write(|w| w.0 = 0); - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; + info.regs.uartimsc().write(|w| w.0 = 0); + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; } Self { + info, + dma_state, rx_dma, phantom: PhantomData, } @@ -295,7 +318,7 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { /// encountered. in both cases, `len` is the number of *good* bytes copied into /// `buffer`. fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { - let r = T::regs(); + let r = self.info.regs; for (i, b) in buffer.iter_mut().enumerate() { if r.uartfr().read().rxfe() { return Ok(i); @@ -319,12 +342,12 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { } } -impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { +impl<'d, M: Mode> Drop for UartRx<'d, M> { fn drop(&mut self) { if self.rx_dma.is_some() { - T::Interrupt::disable(); + self.info.interrupt.disable(); // clear dma flags. irq handlers use these to disambiguate among themselves. - T::regs().uartdmacr().write_clear(|reg| { + self.info.regs.uartdmacr().write_clear(|reg| { reg.set_rxdmae(true); reg.set_txdmae(true); reg.set_dmaonerr(true); @@ -333,28 +356,26 @@ impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { } } -impl<'d, T: Instance> UartRx<'d, T, Blocking> { +impl<'d> UartRx<'d, Blocking> { /// Create a new UART RX instance for blocking mode operations. - pub fn new_blocking( - _uart: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - config: Config, - ) -> Self { - into_ref!(rx); - Uart::::init(None, Some(rx.map_into()), None, None, config); - Self::new_inner(false, None) + pub fn new_blocking(_uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner(T::info(), T::dma_state(), false, None) } /// Convert this uart RX instance into a buffered uart using the provided /// irq and receive buffer. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, rx_buffer: &'d mut [u8], - ) -> BufferedUartRx<'d, T> { - buffered::init_buffers::(irq, None, Some(rx_buffer)); + ) -> BufferedUartRx { + buffered::init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); - BufferedUartRx { phantom: PhantomData } + BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + } } } @@ -365,7 +386,7 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let uart = T::regs(); + let uart = T::info().regs; if !uart.uartdmacr().read().rxdmae() { return; } @@ -381,13 +402,13 @@ impl interrupt::typelevel::Handler for InterruptHandl } } -impl<'d, T: Instance> UartRx<'d, T, Async> { +impl<'d> UartRx<'d, Async> { /// Read from UART RX into the provided buffer. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { // clear error flags before we drain the fifo. errors that have accumulated // in the flags will also be present in the fifo. - T::dma_state().rx_errs.store(0, Ordering::Relaxed); - T::regs().uarticr().write(|w| { + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { w.set_oeic(true); w.set_beic(true); w.set_peic(true); @@ -408,29 +429,34 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // start a dma transfer. if errors have happened in the interim some error // interrupt flags will have been raised, and those will be picked up immediately // by the interrupt handler. - let ch = self.rx_dma.as_mut().unwrap(); - T::regs().uartimsc().write_set(|w| { + let ch = self.rx_dma.as_mut().unwrap().reborrow(); + self.info.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| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_rxdmae(true); reg.set_dmaonerr(true); }); let transfer = unsafe { // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - crate::dma::read(ch, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ.into()) + crate::dma::read( + ch, + self.info.regs.uartdr().as_ptr() as *const _, + buffer, + self.info.rx_dreq.into(), + ) }; // 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) { + self.dma_state.rx_err_waker.register(cx.waker()); + match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -442,7 +468,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { 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) + Uartris(self.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 @@ -522,8 +548,8 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { ) -> Result { // 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| { + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { w.set_oeic(true); w.set_beic(true); w.set_peic(true); @@ -555,14 +581,14 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // 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| { + let ch = self.rx_dma.as_mut().unwrap(); + self.info.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| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_rxdmae(true); reg.set_dmaonerr(true); }); @@ -572,10 +598,10 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // 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 _, + ch.reborrow(), + self.info.regs.uartdr().as_ptr() as *const _, sbuffer, - T::RX_DREQ.into(), + self.info.rx_dreq.into(), ) }; @@ -583,8 +609,8 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { 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) { + self.dma_state.rx_err_waker.register(cx.waker()); + match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -597,7 +623,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { 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) + Uartris(self.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 @@ -636,7 +662,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { continue; } - let regs = T::regs(); + let regs = self.info.regs; let all_full = next_addr == eval; // NOTE: This is off label usage of RSR! See the issue below for @@ -686,44 +712,32 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { } } -impl<'d, T: Instance> Uart<'d, T, Blocking> { +impl<'d> Uart<'d, Blocking> { /// Create a new UART without hardware flow control - pub fn new_blocking( - uart: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - rx: impl Peripheral

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

+ 'd, - tx: impl Peripheral

> + 'd, - rx: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, + pub fn new_with_rtscts_blocking( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, config: Config, ) -> Self { - into_ref!(tx, rx, cts, rts); Self::new_inner( uart, - tx.map_into(), - rx.map_into(), - Some(rts.map_into()), - Some(cts.map_into()), + tx.into(), + rx.into(), + Some(rts.into()), + Some(cts.into()), false, None, None, @@ -733,86 +747,91 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { /// Convert this uart instance into a buffered uart using the provided /// irq, transmit and receive buffers. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], - ) -> BufferedUart<'d, T> { - buffered::init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + ) -> BufferedUart { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); BufferedUart { - rx: BufferedUartRx { phantom: PhantomData }, - tx: BufferedUartTx { phantom: PhantomData }, + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, } } } -impl<'d, T: Instance> Uart<'d, T, Async> { +impl<'d> Uart<'d, Async> { /// Create a new DMA enabled UART without hardware flow control - pub fn new( - uart: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - rx: impl Peripheral

> + 'd, + pub fn new( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, _irq: impl Binding>, - tx_dma: impl Peripheral

+ 'd, - rx_dma: impl Peripheral

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

+ 'd, - tx: impl Peripheral

> + 'd, - rx: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, + pub fn new_with_rtscts( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, _irq: impl Binding>, - tx_dma: impl Peripheral

+ 'd, - rx_dma: impl Peripheral

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

+ 'd, - mut tx: PeripheralRef<'d, AnyPin>, - mut rx: PeripheralRef<'d, AnyPin>, - mut rts: Option>, - mut cts: Option>, +impl<'d, M: Mode> Uart<'d, M> { + fn new_inner( + _uart: Peri<'d, T>, + mut tx: Peri<'d, AnyPin>, + mut rx: Peri<'d, AnyPin>, + mut rts: Option>, + mut cts: Option>, has_irq: bool, - tx_dma: Option>, - rx_dma: Option>, + tx_dma: Option>, + rx_dma: Option>, config: Config, ) -> Self { Self::init( + T::info(), Some(tx.reborrow()), Some(rx.reborrow()), rts.as_mut().map(|x| x.reborrow()), @@ -821,19 +840,20 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { ); Self { - tx: UartTx::new_inner(tx_dma), - rx: UartRx::new_inner(has_irq, rx_dma), + tx: UartTx::new_inner(T::info(), tx_dma), + rx: UartRx::new_inner(T::info(), T::dma_state(), has_irq, rx_dma), } } fn init( - tx: Option>, - rx: Option>, - rts: Option>, - cts: Option>, + info: &Info, + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, config: Config, ) { - let r = T::regs(); + let r = info.regs; if let Some(pin) = &tx { let funcsel = { let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; @@ -911,7 +931,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); } - Self::set_baudrate_inner(config.baudrate); + Self::set_baudrate_inner(info, config.baudrate); let (pen, eps) = match config.parity { Parity::ParityNone => (false, false), @@ -928,7 +948,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); r.uartifls().write(|w| { - w.set_rxiflsel(0b000); + w.set_rxiflsel(0b100); w.set_txiflsel(0b000); }); @@ -941,8 +961,8 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); } - fn lcr_modify(f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { - let r = T::regs(); + fn lcr_modify(info: &Info, f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { + let r = info.regs; // Notes from PL011 reference manual: // @@ -993,11 +1013,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { /// sets baudrate on runtime pub fn set_baudrate(&mut self, baudrate: u32) { - Self::set_baudrate_inner(baudrate); + Self::set_baudrate_inner(self.tx.info, baudrate); } - fn set_baudrate_inner(baudrate: u32) { - let r = T::regs(); + fn set_baudrate_inner(info: &Info, baudrate: u32) { + let r = info.regs; let clk_base = crate::clocks::clk_peri_freq(); @@ -1017,11 +1037,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); - Self::lcr_modify(|_| {}); + Self::lcr_modify(info, |_| {}); } } -impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { +impl<'d, M: Mode> Uart<'d, M> { /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buffer) @@ -1049,19 +1069,19 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { /// Split the Uart into a transmitter and receiver, which is particularly /// useful when having two tasks correlating to transmitting and receiving. - pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) { + pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { (self.tx, self.rx) } /// Split the Uart into a transmitter and receiver by mutable reference, /// which is particularly useful when having two tasks correlating to /// transmitting and receiving. - pub fn split_ref(&mut self) -> (&mut UartTx<'d, T, M>, &mut UartRx<'d, T, M>) { + pub fn split_ref(&mut self) -> (&mut UartTx<'d, M>, &mut UartRx<'d, M>) { (&mut self.tx, &mut self.rx) } } -impl<'d, T: Instance> Uart<'d, T, Async> { +impl<'d> Uart<'d, Async> { /// Write to UART TX from the provided buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.write(buffer).await @@ -1091,10 +1111,10 @@ impl<'d, T: Instance> Uart<'d, T, Async> { } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, M> { type Error = Error; fn read(&mut self) -> Result> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -1115,11 +1135,11 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, M> { type Error = Error; fn write(&mut self, word: u8) -> Result<(), nb::Error> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().txff() { return Err(nb::Error::WouldBlock); } @@ -1129,7 +1149,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, } fn flush(&mut self) -> Result<(), nb::Error> { - let r = T::regs(); + let r = self.info.regs; if !r.uartfr().read().txfe() { return Err(nb::Error::WouldBlock); } @@ -1137,7 +1157,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, M> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { @@ -1149,7 +1169,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Read for Uart<'d, M> { type Error = Error; fn read(&mut self) -> Result> { @@ -1157,7 +1177,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Write for Uart<'d, M> { type Error = Error; fn write(&mut self, word: u8) -> Result<(), nb::Error> { @@ -1169,7 +1189,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, M> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { @@ -1192,21 +1212,21 @@ impl embedded_hal_nb::serial::Error for Error { } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, M> { fn read(&mut self) -> nb::Result { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -1227,7 +1247,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) } @@ -1237,13 +1257,27 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, T, M> { +impl<'d> embedded_io::ErrorType for UartTx<'d, Blocking> { + type Error = Error; +} + +impl<'d> embedded_io::Write for UartTx<'d, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, M> { fn read(&mut self) -> Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) } @@ -1253,13 +1287,31 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> } } +impl<'d> embedded_io::ErrorType for Uart<'d, Blocking> { + type Error = Error; +} + +impl<'d> embedded_io::Write for Uart<'d, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +struct Info { + regs: pac::uart::Uart, + tx_dreq: pac::dma::vals::TreqSel, + rx_dreq: pac::dma::vals::TreqSel, + interrupt: Interrupt, +} + trait SealedMode {} trait SealedInstance { - const TX_DREQ: u8; - const RX_DREQ: u8; - - fn regs() -> pac::uart::Uart; + fn info() -> &'static Info; fn buffered_state() -> &'static buffered::State; @@ -1287,7 +1339,7 @@ impl_mode!(Async); /// UART instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance { +pub trait Instance: SealedInstance + PeripheralType { /// Interrupt for this instance. type Interrupt: interrupt::typelevel::Interrupt; } @@ -1295,11 +1347,14 @@ pub trait Instance: SealedInstance { macro_rules! impl_instance { ($inst:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { impl SealedInstance for peripherals::$inst { - const TX_DREQ: u8 = $tx_dreq; - const RX_DREQ: u8 = $rx_dreq; - - fn regs() -> pac::uart::Uart { - pac::$inst + fn info() -> &'static Info { + static INFO: Info = Info { + regs: pac::$inst, + tx_dreq: $tx_dreq, + rx_dreq: $rx_dreq, + interrupt: crate::interrupt::typelevel::$irq::IRQ, + }; + &INFO } fn buffered_state() -> &'static buffered::State { @@ -1321,8 +1376,18 @@ macro_rules! impl_instance { }; } -impl_instance!(UART0, UART0_IRQ, 20, 21); -impl_instance!(UART1, UART1_IRQ, 22, 23); +impl_instance!( + UART0, + UART0_IRQ, + pac::dma::vals::TreqSel::UART0_TX, + pac::dma::vals::TreqSel::UART0_RX +); +impl_instance!( + UART1, + UART1_IRQ, + pac::dma::vals::TreqSel::UART1_TX, + pac::dma::vals::TreqSel::UART1_RX +); /// Trait for TX pins. pub trait TxPin: crate::gpio::Pin {} diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 20ef881f9..96541ade6 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -5,6 +5,7 @@ use core::slice; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use embassy_usb_driver as driver; use embassy_usb_driver::{ @@ -12,7 +13,7 @@ use embassy_usb_driver::{ }; use crate::interrupt::typelevel::{Binding, Interrupt}; -use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; +use crate::{interrupt, pac, peripherals, Peri, RegExt}; trait SealedInstance { fn regs() -> crate::pac::usb::Usb; @@ -21,7 +22,7 @@ trait SealedInstance { /// USB peripheral instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static { +pub trait Instance: SealedInstance + PeripheralType + 'static { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } @@ -43,10 +44,9 @@ const EP_COUNT: usize = 16; const EP_MEMORY_SIZE: usize = 4096; const EP_MEMORY: *mut u8 = pac::USB_DPRAM.as_ptr() as *mut u8; -const NEW_AW: AtomicWaker = AtomicWaker::new(); -static BUS_WAKER: AtomicWaker = NEW_AW; -static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; -static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; +static BUS_WAKER: AtomicWaker = AtomicWaker::new(); +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; struct EndpointBuffer { addr: u16, @@ -108,7 +108,7 @@ pub struct Driver<'d, T: Instance> { impl<'d, T: Instance> Driver<'d, T> { /// Create a new USB driver. - pub fn new(_usb: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { + pub fn new(_usb: Peri<'d, T>, _irq: impl Binding>) -> Self { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; diff --git a/embassy-rp/src/watchdog.rs b/embassy-rp/src/watchdog.rs index edd48e0e0..49cf03850 100644 --- a/embassy-rp/src/watchdog.rs +++ b/embassy-rp/src/watchdog.rs @@ -10,8 +10,17 @@ use core::marker::PhantomData; use embassy_time::Duration; -use crate::pac; use crate::peripherals::WATCHDOG; +use crate::{pac, Peri}; + +/// The reason for a system reset from the watchdog. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ResetReason { + /// The reset was forced. + Forced, + /// The watchdog was not fed in time. + TimedOut, +} /// Watchdog peripheral pub struct Watchdog { @@ -21,7 +30,7 @@ pub struct Watchdog { impl Watchdog { /// Create a new `Watchdog` - pub fn new(_watchdog: WATCHDOG) -> Self { + pub fn new(_watchdog: Peri<'static, WATCHDOG>) -> Self { Self { phantom: PhantomData, load_value: 0, @@ -80,17 +89,25 @@ impl Watchdog { /// Start the watchdog timer pub fn start(&mut self, period: Duration) { + #[cfg(feature = "rp2040")] + const MAX_PERIOD: u32 = 0xFFFFFF / 2; + #[cfg(feature = "_rp235x")] const MAX_PERIOD: u32 = 0xFFFFFF; let delay_us = period.as_micros(); - if delay_us > (MAX_PERIOD / 2) as u64 { - panic!("Period cannot exceed {} microseconds", MAX_PERIOD / 2); + if delay_us > (MAX_PERIOD) as u64 { + panic!("Period cannot exceed {} microseconds", MAX_PERIOD); } let delay_us = delay_us as u32; // Due to a logic error, the watchdog decrements by 2 and // the load value must be compensated; see RP2040-E1 - self.load_value = delay_us * 2; + // This errata is fixed in the RP235x + if cfg!(feature = "rp2040") { + self.load_value = delay_us * 2; + } else { + self.load_value = delay_us; + } self.enable(false); self.configure_wdog_reset_triggers(); @@ -140,4 +157,17 @@ impl Watchdog { _ => panic!("Invalid watchdog scratch index"), } } + + /// Get the reason for the last system reset, if it was caused by the watchdog. + pub fn reset_reason(&self) -> Option { + let watchdog = pac::WATCHDOG; + let reason = watchdog.reason().read(); + if reason.force() { + Some(ResetReason::Forced) + } else if reason.timer() { + Some(ResetReason::TimedOut) + } else { + None + } + } } diff --git a/embassy-stm32-wpan/Cargo.toml b/embassy-stm32-wpan/Cargo.toml index 54dfd39a6..b749625aa 100644 --- a/embassy-stm32-wpan/Cargo.toml +++ b/embassy-stm32-wpan/Cargo.toml @@ -13,21 +13,21 @@ documentation = "https://docs.embassy.dev/embassy-stm32-wpan" src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-wpan-v$VERSION/embassy-stm32-wpan/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32-wpan/src/" target = "thumbv7em-none-eabihf" -features = ["stm32wb55rg"] +features = ["stm32wb55rg", "ble", "mac"] [package.metadata.docs.rs] -features = ["stm32wb55rg"] +features = ["stm32wb55rg", "ble", "mac"] [dependencies] -embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } +embassy-stm32 = { version = "0.2.0", path = "../embassy-stm32" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" } -embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver", optional=true } -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.17", optional = true } cortex-m = "0.7.6" diff --git a/embassy-stm32-wpan/src/consts.rs b/embassy-stm32-wpan/src/consts.rs index 6aaef1d35..e2ae6ca86 100644 --- a/embassy-stm32-wpan/src/consts.rs +++ b/embassy-stm32-wpan/src/consts.rs @@ -69,7 +69,7 @@ pub const TL_CS_EVT_SIZE: usize = core::mem::size_of::(); * enough to store all asynchronous events received in between. * When CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE is set to 27, this allow to store three 255 bytes long asynchronous events * between the HCI command and its event. - * This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is to small, + * This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is too small, * the system may hang if the queue is full with asynchronous events and the HCI layer is still waiting * for a CC/CS event, In that case, the notification TL_BLE_HCI_ToNot() is called to indicate * to the application a HCI command did not receive its command event within 30s (Default HCI Timeout). diff --git a/embassy-stm32-wpan/src/lib.rs b/embassy-stm32-wpan/src/lib.rs index f9560d235..40ff14795 100644 --- a/embassy-stm32-wpan/src/lib.rs +++ b/embassy-stm32-wpan/src/lib.rs @@ -1,7 +1,21 @@ +//! The embassy-stm32-wpan crate aims to provide safe use of the commands necessary to interface +//! with the Cortex C0 CPU2 coprocessor of STM32WB microcontrollers. It implements safe wrappers +//! around the Transport Layer, and in particular the system, memory, BLE and Mac channels. +//! +//! # Design +//! +//! This crate loosely follows the Application Note 5289 "How to build wireless applications with +//! STM32WB MCUs"; several of the startup procedures laid out in Annex 14.1 are implemented using +//! inline copies of the code contained within the `stm32wb_copro` C library. +//! +//! BLE commands are implemented via use of the [stm32wb_hci] crate, for which the +//! [stm32wb_hci::Controller] trait has been implemented. + #![no_std] #![allow(async_fn_in_trait)] #![doc = include_str!("../README.md")] // #![warn(missing_docs)] +#![allow(static_mut_refs)] // TODO: Fix // This must go FIRST so that all the other modules see its macros. mod fmt; @@ -9,7 +23,7 @@ mod fmt; use core::mem::MaybeUninit; use core::sync::atomic::{compiler_fence, Ordering}; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::Peri; use embassy_stm32::interrupt; use embassy_stm32::ipcc::{Config, Ipcc, ReceiveInterruptHandler, TransmitInterruptHandler}; use embassy_stm32::peripherals::IPCC; @@ -36,8 +50,9 @@ pub use crate::sub::ble::hci; type PacketHeader = LinkedListNode; +/// Transport Layer for the Mailbox interface pub struct TlMbox<'d> { - _ipcc: PeripheralRef<'d, IPCC>, + _ipcc: Peri<'d, IPCC>, pub sys_subsystem: Sys, pub mm_subsystem: MemoryManager, @@ -48,14 +63,43 @@ pub struct TlMbox<'d> { } impl<'d> TlMbox<'d> { + /// Initialise the Transport Layer, and creates and returns a wrapper around it. + /// + /// This method performs the initialisation laid out in AN5289 annex 14.1. However, it differs + /// from the implementation documented in Figure 64, to avoid needing to reference any C + /// function pointers. + /// + /// Annex 14.1 lays out the following methods that should be called: + /// 1. tl_mbox.c/TL_Init, which initialises the reference table that is shared between CPU1 + /// and CPU2. + /// 2. shci_tl.c/shci_init(), which initialises the system transport layer, and in turn + /// calls tl_mbox.c/TL_SYS_Init, which initialises SYSTEM_EVT_QUEUE channel. + /// 3. tl_mbox.c/TL_MM_Init(), which initialises the channel used for sending memory + /// manager commands. + /// 4. tl_mbox.c/TL_Enable(), which enables the IPCC, and starts CPU2. + /// This implementation initialises all of the shared refernce tables and all IPCC channel that + /// would be initialised by this process. The developer should therefore treat this method as + /// completing all steps in Figure 64. + /// + /// Once this method has been called, no system commands may be sent until the CPU2 ready + /// signal is received, via [sys_subsystem.read]; this completes the procedure laid out in + /// Figure 65. + /// + /// If the `ble` feature is enabled, at this point, the user should call + /// [sys_subsystem.shci_c2_ble_init], before any commands are written to the + /// [TlMbox.ble_subsystem] ([sub::ble::Ble::new()] completes the process that would otherwise + /// be handled by `TL_BLE_Init`; see Figure 66). This completes the procedure laid out in + /// Figure 66. + // TODO: document what the user should do after calling init to use the mac_802_15_4 subsystem pub fn init( - ipcc: impl Peripheral

+ 'd, + ipcc: Peri<'d, IPCC>, _irqs: impl interrupt::typelevel::Binding + interrupt::typelevel::Binding, config: Config, ) -> Self { - into_ref!(ipcc); - + // this is an inlined version of TL_Init from the STM32WB firmware as requested by AN5289. + // HW_IPCC_Init is not called, and its purpose is (presumably?) covered by this + // implementation unsafe { TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable { device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(), @@ -139,6 +183,7 @@ impl<'d> TlMbox<'d> { compiler_fence(Ordering::SeqCst); + // this is equivalent to `HW_IPCC_Enable`, which is called by `TL_Enable` Ipcc::enable(config); Self { diff --git a/embassy-stm32-wpan/src/mac/commands.rs b/embassy-stm32-wpan/src/mac/commands.rs index c97c609c3..82b9d2772 100644 --- a/embassy-stm32-wpan/src/mac/commands.rs +++ b/embassy-stm32-wpan/src/mac/commands.rs @@ -371,7 +371,7 @@ pub struct DataRequest { } impl DataRequest { - pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &mut Self { + pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &'a mut Self { self.msdu_ptr = buf as *const _ as *const u8; self.msdu_length = buf.len() as u8; diff --git a/embassy-stm32-wpan/src/mac/driver.rs b/embassy-stm32-wpan/src/mac/driver.rs index 5b9d5daf4..41cca09e3 100644 --- a/embassy-stm32-wpan/src/mac/driver.rs +++ b/embassy-stm32-wpan/src/mac/driver.rs @@ -23,8 +23,14 @@ impl<'d> Driver<'d> { impl<'d> embassy_net_driver::Driver for Driver<'d> { // type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; // type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; - type RxToken<'a> = RxToken<'d> where Self: 'a; - type TxToken<'a> = TxToken<'d> where Self: 'a; + type RxToken<'a> + = RxToken<'d> + where + Self: 'a; + type TxToken<'a> + = TxToken<'d> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { if self.runner.rx_channel.poll_ready_to_receive(cx).is_ready() diff --git a/embassy-stm32-wpan/src/sub/ble.rs b/embassy-stm32-wpan/src/sub/ble.rs index c5f2334f4..0f770d92c 100644 --- a/embassy-stm32-wpan/src/sub/ble.rs +++ b/embassy-stm32-wpan/src/sub/ble.rs @@ -11,11 +11,39 @@ use crate::tables::{BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA use crate::unsafe_linked_list::LinkedListNode; use crate::{channels, evt}; +/// A guard that, once constructed, may be used to send BLE commands to CPU2. +/// +/// It is the responsibility of the caller to ensure that they have awaited an event via +/// [crate::sub::sys::Sys::read] before sending any of these commands, and to call +/// [crate::sub::sys::Sys::shci_c2_ble_init] and await the HCI_COMMAND_COMPLETE_EVENT before +/// sending any other commands. +/// +/// # Example +/// +/// ``` +/// # embassy_stm32::bind_interrupts!(struct Irqs{ +/// # IPCC_C1_RX => ReceiveInterruptHandler; +/// # IPCC_C1_TX => TransmitInterruptHandler; +/// # }); +/// # +/// # let p = embassy_stm32::init(embassy_stm32::Config::default()); +/// # let mut mbox = embassy_stm32_wpan::TlMbox::init(p.IPCC, Irqs, embassy_stm32::ipcc::Config::default()); +/// # +/// # let sys_event = mbox.sys_subsystem.read().await; +/// # let _command_status = mbox.sys_subsystem.shci_c2_ble_init(Default::default()); +/// # // BLE commands may now be sent +/// # +/// # mbox.ble_subsystem.reset().await; +/// # let _reset_response = mbox.ble_subsystem.read().await; +/// ``` pub struct Ble { _private: (), } impl Ble { + /// Constructs a guard that allows for BLE commands to be sent to CPU2. + /// + /// This takes the place of `TL_BLE_Init`, completing that step as laid out in AN5289, Fig 66. pub(crate) fn new() -> Self { unsafe { LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); @@ -30,6 +58,7 @@ impl Ble { Self { _private: () } } + /// `HW_IPCC_BLE_EvtNot` pub async fn tl_read(&self) -> EvtBox { Ipcc::receive(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, || unsafe { diff --git a/embassy-stm32-wpan/src/sub/sys.rs b/embassy-stm32-wpan/src/sub/sys.rs index bd2ea3f74..cf6df58bf 100644 --- a/embassy-stm32-wpan/src/sub/sys.rs +++ b/embassy-stm32-wpan/src/sub/sys.rs @@ -10,6 +10,7 @@ use crate::tables::{SysTable, WirelessFwInfoTable}; use crate::unsafe_linked_list::LinkedListNode; use crate::{channels, Ipcc, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_DEVICE_INFO_TABLE, TL_SYS_TABLE}; +/// A guard that, once constructed, allows for sys commands to be sent to CPU2. pub struct Sys { _private: (), } @@ -86,12 +87,22 @@ impl Sys { self.write_and_get_response(ShciOpcode::Mac802_15_4Init, &[]).await } + /// Send a request to CPU2 to initialise the BLE stack. + /// + /// This must be called before any BLE commands are sent via the BLE channel (according to + /// AN5289, Figures 65 and 66). It should only be called after CPU2 sends a system event, via + /// `HW_IPCC_SYS_EvtNot`, aka `IoBusCallBackUserEvt` (as detailed in Figure 65), aka + /// [crate::sub::ble::hci::host::uart::UartHci::read]. #[cfg(feature = "ble")] pub async fn shci_c2_ble_init(&self, param: ShciBleInitCmdParam) -> Result { self.write_and_get_response(ShciOpcode::BleInit, param.payload()).await } /// `HW_IPCC_SYS_EvtNot` + /// + /// This method takes the place of the `HW_IPCC_SYS_EvtNot`/`SysUserEvtRx`/`APPE_SysUserEvtRx`, + /// as the embassy implementation avoids the need to call C public bindings, and instead + /// handles the event channels directly. pub async fn read(&self) -> EvtBox { Ipcc::receive(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, || unsafe { if let Some(node_ptr) = LinkedListNode::remove_head(SYSTEM_EVT_QUEUE.as_mut_ptr()) { diff --git a/embassy-stm32-wpan/src/tables.rs b/embassy-stm32-wpan/src/tables.rs index f2c250527..fe6fc47a3 100644 --- a/embassy-stm32-wpan/src/tables.rs +++ b/embassy-stm32-wpan/src/tables.rs @@ -25,17 +25,17 @@ pub struct RssInfoTable { /** * Version - * [0:3] = Build - 0: Untracked - 15:Released - x: Tracked version - * [4:7] = branch - 0: Mass Market - x: ... - * [8:15] = Subversion - * [16:23] = Version minor - * [24:31] = Version major + * \[0:3\] = Build - 0: Untracked - 15:Released - x: Tracked version + * \[4:7\] = branch - 0: Mass Market - x: ... + * \[8:15\] = Subversion + * \[16:23\] = Version minor + * \[24:31\] = Version major * * Memory Size - * [0:7] = Flash ( Number of 4k sector) - * [8:15] = Reserved ( Shall be set to 0 - may be used as flash extension ) - * [16:23] = SRAM2b ( Number of 1k sector) - * [24:31] = SRAM2a ( Number of 1k sector) + * \[0:7\] = Flash ( Number of 4k sector) + * \[8:15\] = Reserved ( Shall be set to 0 - may be used as flash extension ) + * \[16:23\] = SRAM2b ( Number of 1k sector) + * \[24:31\] = SRAM2a ( Number of 1k sector) */ #[derive(Debug, Copy, Clone)] #[repr(C, packed)] @@ -89,12 +89,19 @@ pub struct DeviceInfoTable { pub wireless_fw_info_table: WirelessFwInfoTable, } +/// The bluetooth reference table, as defined in figure 67 of STM32WX AN5289. #[derive(Debug)] #[repr(C)] pub struct BleTable { + /// A pointer to the buffer that is used for sending BLE commands. pub pcmd_buffer: *mut CmdPacket, + /// A pointer to the buffer used for storing Command statuses. pub pcs_buffer: *const u8, + /// A pointer to the event queue, over which IPCC BLE events are sent. This may be accessed via + /// [crate::sub::ble::Ble::tl_read]. pub pevt_queue: *const u8, + /// A pointer to the buffer that is used for sending HCI (Host-Controller Interface) ACL + /// (Asynchronous Connection-oriented Logical transport) commands (unused). pub phci_acl_data_buffer: *mut AclDataPacket, } @@ -271,6 +278,6 @@ pub static mut BLE_SPARE_EVT_BUF: Aligned> = Aligned(MaybeUninit::uninit()); diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md new file mode 100644 index 000000000..b6781905e --- /dev/null +++ b/embassy-stm32/CHANGELOG.md @@ -0,0 +1,279 @@ +# Changelog for embassy-stm32 + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased +- Modify BufferedUart initialization to take pins before interrupts ([#3983](https://github.com/embassy-rs/embassy/pull/3983)) +- Added a 'single-bank' and a 'dual-bank' feature so chips with configurable flash bank setups are be supported in embassy ([#4125](https://github.com/embassy-rs/embassy/pull/4125)) + +## 0.2.0 - 2025-01-10 + +Starting 2025 strong with a release packed with new, exciting good stuff! 🚀 + +### New chips + +This release adds support for many newly-released STM32 chips. + +- STM32H7[RS] "bootflash line" ([#2898](https://github.com/embassy-rs/embassy/pull/2898)) +- STM32U0 ([#2809](https://github.com/embassy-rs/embassy/pull/2809) [#2813](https://github.com/embassy-rs/embassy/pull/2813)) +- STM32H5[23] ([#2892](https://github.com/embassy-rs/embassy/pull/2892)) +- STM32U5[FG] ([#2892](https://github.com/embassy-rs/embassy/pull/2892)) +- STM32WBA5[045] ([#2892](https://github.com/embassy-rs/embassy/pull/2892)) + +### Simpler APIs with less generics + +Many HAL APIs have been simplified thanks to reducing the amount of generic parameters. This helps with creating arrays of pins or peripherals, and for calling the same code with different pins/peripherals without incurring in code size penalties d + +For GPIO, the pins have been eliminated. `Output<'_, PA4>` is now `Output<'_>`. + +For peripherals, both pins and DMA channels have been eliminated. Peripherals now have a "mode" generic param that specifies whether it's capable of async operation. For example, `I2c<'_, I2C2, NoDma, NoDma>` is now `I2c<'_, Blocking>` and `I2c<'_, I2C2, DMA2_CH1, DMA2_CH2>` is now `I2c<'_, Async>`. + +- Removed DMA channel generic params for UART ([#2821](https://github.com/embassy-rs/embassy/pull/2821)), I2C ([#2820](https://github.com/embassy-rs/embassy/pull/2820)), SPI ([#2819](https://github.com/embassy-rs/embassy/pull/2819)), QSPI ([#2982](https://github.com/embassy-rs/embassy/pull/2982)), OSPI ([#2941](https://github.com/embassy-rs/embassy/pull/2941)). +- Removed peripheral generic params for GPIO ([#2471](https://github.com/embassy-rs/embassy/pull/2471)), UART ([#2836](https://github.com/embassy-rs/embassy/pull/2836)), I2C ([#2974](https://github.com/embassy-rs/embassy/pull/2974)), SPI ([#2835](https://github.com/embassy-rs/embassy/pull/2835)) +- Remove generics in CAN ([#3012](https://github.com/embassy-rs/embassy/pull/3012), [#3020](https://github.com/embassy-rs/embassy/pull/3020), [#3032](https://github.com/embassy-rs/embassy/pull/3032), [#3033](https://github.com/embassy-rs/embassy/pull/3033)) + +### More complete and consistent RCC + +RCC support has been vastly expanded and improved. +- The API is now consistent across all STM32 families. Previously in some families you'd configure the desired target frequencies for `sysclk` and the buses and `embassy-stm32` would try to calculate dividers and muxes to hit them as close as possible. This has proved to be intractable in the general case and hard to extend to more exotic RCC configurations. So, we have standardized on an API where the user specifies the settings for dividers and muxes directly. It's lower level but gices more control to the user, supports all edge case exotic configurations, and makes it easier to translate a configuration from the STM32CubeMX tool. ([Tracking issue](https://github.com/embassy-rs/embassy/issues/2515). [#2624](https://github.com/embassy-rs/embassy/pull/2624). F0, F1 [#2564](https://github.com/embassy-rs/embassy/pull/2564), F3 [#2560](https://github.com/embassy-rs/embassy/pull/2560), U5 [#2617](https://github.com/embassy-rs/embassy/pull/2617), [#3514](https://github.com/embassy-rs/embassy/pull/3514), [#3513](https://github.com/embassy-rs/embassy/pull/3513), G4 [#2579](https://github.com/embassy-rs/embassy/pull/2579), [#2618](https://github.com/embassy-rs/embassy/pull/2618), WBA [#2520](https://github.com/embassy-rs/embassy/pull/2520), G0, C0 ([#2656](https://github.com/embassy-rs/embassy/pull/2656)). +- Added support for configuring all per-peripheral clock muxes (CCIPRx, DCKCFGRx registers) in `config.rcc.mux`. This was previously handled in an ad-hoc way in some drivers (e.g. USB) and not at all in others (causing e.g. wrong SPI frequency) ([#2521](https://github.com/embassy-rs/embassy/pull/2521), [#2583](https://github.com/embassy-rs/embassy/pull/2583), [#2634](https://github.com/embassy-rs/embassy/pull/2634), [#2626](https://github.com/embassy-rs/embassy/pull/2626), [#2815](https://github.com/embassy-rs/embassy/pull/2815), [#2517](https://github.com/embassy-rs/embassy/pull/2517)). +- Switch to a safe configuration before configuring RCC. This helps avoid crashes when RCC has been already configured previously (for example by a bootloader). (F2, F4, F7 [#2829](https://github.com/embassy-rs/embassy/pull/2829), C0, F0, F1, F3, G0, G4, H5, H7[#3008](https://github.com/embassy-rs/embassy/pull/3008)) +- Some new nice features: + - Expose RCC enable and disable in public API. ([#2807](https://github.com/embassy-rs/embassy/pull/2807)) + - Add `unchecked-overclocking` feature that disables all asserts, allowing running RCC out of spec. ([#3574](https://github.com/embassy-rs/embassy/pull/3574)) +- Many fixes: + - Workaround H5 errata that accidentally clears RAM on backup domain reset. ([#2616](https://github.com/embassy-rs/embassy/pull/2616)) + - Reset RTC on L0 ([#2597](https://github.com/embassy-rs/embassy/pull/2597)) + - Fix H7 to use correct unit in vco clock check ([#2537](https://github.com/embassy-rs/embassy/pull/2537)) + - Fix incorrect D1CPRE max for STM32H7 RM0468 ([#2518](https://github.com/embassy-rs/embassy/pull/2518)) + - WBA's high speed external clock has to run at 32 MHz ([#2511](https://github.com/embassy-rs/embassy/pull/2511)) + - Take into account clock propagation delay to peripherals after enabling a clock. ([#2677](https://github.com/embassy-rs/embassy/pull/2677)) + - Fix crash caused by using higher MSI range as sysclk on STM32WL ([#2786](https://github.com/embassy-rs/embassy/pull/2786)) + - fix using HSI48 as SYSCLK on F0 devices with CRS ([#3652](https://github.com/embassy-rs/embassy/pull/3652)) + - compute LSE and LSI frequency for STM32L and STM32U0 series ([#3554](https://github.com/embassy-rs/embassy/pull/3554)) + - Add support for LSESYS, used to pass LSE clock to peripherals ([#3518](https://github.com/embassy-rs/embassy/pull/3518)) + - H5: LSE low drive mode is not functional ([#2738](https://github.com/embassy-rs/embassy/pull/2738)) + +### New peripheral drivers + +- Dual-core support. First core initializes RCC and writes a struct into shared memory that the second core uses, ensuring no conflicts. ([#3158](https://github.com/embassy-rs/embassy/pull/3158), [#3263](https://github.com/embassy-rs/embassy/pull/3263), [#3687](https://github.com/embassy-rs/embassy/pull/3687)) +- USB Type-C/USB Power Delivery Interface (UCPD) ([#2652](https://github.com/embassy-rs/embassy/pull/2652), [#2683](https://github.com/embassy-rs/embassy/pull/2683), [#2701](https://github.com/embassy-rs/embassy/pull/2701), [#2925](https://github.com/embassy-rs/embassy/pull/2925), [#3084](https://github.com/embassy-rs/embassy/pull/3084), [#3271](https://github.com/embassy-rs/embassy/pull/3271), [#3678](https://github.com/embassy-rs/embassy/pull/3678), [#3714](https://github.com/embassy-rs/embassy/pull/3714)) +- Touch sensing controller (TSC) ([#2853](https://github.com/embassy-rs/embassy/pull/2853), [#3111](https://github.com/embassy-rs/embassy/pull/3111), [#3163](https://github.com/embassy-rs/embassy/pull/3163), [#3274](https://github.com/embassy-rs/embassy/pull/3274)) +- Display Serial Interface (DSI) [#2903](https://github.com/embassy-rs/embassy/pull/2903), ([#3082](https://github.com/embassy-rs/embassy/pull/3082)) +- LCD/TFT Display Controller (LTDC) ([#3126](https://github.com/embassy-rs/embassy/pull/3126), [#3458](https://github.com/embassy-rs/embassy/pull/3458)) +- SPDIF receiver (SPDIFRX) ([#3280](https://github.com/embassy-rs/embassy/pull/3280)) +- CORDIC math accelerator ([#2697](https://github.com/embassy-rs/embassy/pull/2697)) +- Digital Temperature Sensor (DTS) ([#3717](https://github.com/embassy-rs/embassy/pull/3717)) +- HMAC accelerator ([#2565](https://github.com/embassy-rs/embassy/pull/2565)) +- Hash accelerator ([#2528](https://github.com/embassy-rs/embassy/pull/2528)) +- Crypto accelerator ([#2619](https://github.com/embassy-rs/embassy/pull/2619), [#2691](https://github.com/embassy-rs/embassy/pull/2691)) +- Semaphore (HSEM) ([#2777](https://github.com/embassy-rs/embassy/pull/2777), [#3161](https://github.com/embassy-rs/embassy/pull/3161)) + +### Improvements to existing drivers + +GPIO: +- Generate singletons only for pins that actually exist. ([#3738](https://github.com/embassy-rs/embassy/pull/3738)) +- Add `set_as_analog` to Flex ([#3017](https://github.com/embassy-rs/embassy/pull/3017)) +- Add `embedded-hal` v0.2 `InputPin` impls for `OutputOpenDrain`. ([#2716](https://github.com/embassy-rs/embassy/pull/2716)) +- Add a config option to make the VDDIO2 supply line valid ([#2737](https://github.com/embassy-rs/embassy/pull/2737)) +- Refactor AfType ([#3031](https://github.com/embassy-rs/embassy/pull/3031)) +- Gpiov1: Do not call set_speed for AFType::Input ([#2996](https://github.com/embassy-rs/embassy/pull/2996)) + +UART: +- Add embedded-io impls ([#2739](https://github.com/embassy-rs/embassy/pull/2739)) +- Add support for changing baud rate ([#3512](https://github.com/embassy-rs/embassy/pull/3512)) +- Add split_ref ([#3500](https://github.com/embassy-rs/embassy/pull/3500)) +- Add data bit selection ([#3595](https://github.com/embassy-rs/embassy/pull/3595)) +- Add RX Pull configuration option ([#3415](https://github.com/embassy-rs/embassy/pull/3415)) +- Add async flush ([#3379](https://github.com/embassy-rs/embassy/pull/3379)) +- Add support for sending breaks ([#3286](https://github.com/embassy-rs/embassy/pull/3286)) +- Disconnect pins on drop ([#3006](https://github.com/embassy-rs/embassy/pull/3006)) +- Half-duplex improvements + - Add half-duplex for all USART versions ([#2833](https://github.com/embassy-rs/embassy/pull/2833)) + - configurable readback for half-duplex. ([#3679](https://github.com/embassy-rs/embassy/pull/3679)) + - Convert uart half_duplex to use user configurable IO ([#3233](https://github.com/embassy-rs/embassy/pull/3233)) + - Fix uart::flush with FIFO at Half-Duplex mode ([#2895](https://github.com/embassy-rs/embassy/pull/2895)) + - Fix Half-Duplex sequential reads and writes ([#3089](https://github.com/embassy-rs/embassy/pull/3089)) + - disable transmitter during during half-duplex flush ([#3299](https://github.com/embassy-rs/embassy/pull/3299)) +- Buffered UART improvements + - Add embedded-io ReadReady impls ([#3179](https://github.com/embassy-rs/embassy/pull/3179), [#3451](https://github.com/embassy-rs/embassy/pull/3451)) + - Add constructors for RS485 ([#3441](https://github.com/embassy-rs/embassy/pull/3441)) + - Fix RingBufferedUartRx hard-resetting DMA after initial error ([#3356](https://github.com/embassy-rs/embassy/pull/3356)) + - Don't teardown during reconfigure ([#2989](https://github.com/embassy-rs/embassy/pull/2989)) + - Wake receive task for each received byte ([#2722](https://github.com/embassy-rs/embassy/pull/2722)) + - Fix dma and idle line detection in ringbuffereduartrx ([#3319](https://github.com/embassy-rs/embassy/pull/3319)) + +SPI: +- Add MISO pullup configuration option ([#2943](https://github.com/embassy-rs/embassy/pull/2943)) +- Add slew rate configuration options ([#3669](https://github.com/embassy-rs/embassy/pull/3669)) +- Fix blocking_write on nosck spi. ([#3035](https://github.com/embassy-rs/embassy/pull/3035)) +- Restrict txonly_nosck to SPIv1, it hangs in other versions. ([#3028](https://github.com/embassy-rs/embassy/pull/3028)) +- Fix non-u8 word sizes. ([#3363](https://github.com/embassy-rs/embassy/pull/3363)) +- Issue correct DMA word length when reading to prevent hang. ([#3362](https://github.com/embassy-rs/embassy/pull/3362)) +- Add proper rxonly support for spi_v3 and force tx dma stream requirements. ([#3007](https://github.com/embassy-rs/embassy/pull/3007)) + +I2C: +- Implement asynchronous transactions ([#2742](https://github.com/embassy-rs/embassy/pull/2742)) +- Implement blocking transactions ([#2713](https://github.com/embassy-rs/embassy/pull/2713)) +- Disconnect pins on drop ([#3006](https://github.com/embassy-rs/embassy/pull/3006)) +- Ensure bus is free before master-write operation ([#3104](https://github.com/embassy-rs/embassy/pull/3104)) +- Add workaround for STM32 i2cv1 errata ([#2887](https://github.com/embassy-rs/embassy/pull/2887)) +- Fix disabling pullup accidentally enabling pulldown ([#3410](https://github.com/embassy-rs/embassy/pull/3410)) + +Flash: +- Add L5 support ([#3423](https://github.com/embassy-rs/embassy/pull/3423)) +- Add H5 support ([#3305](https://github.com/embassy-rs/embassy/pull/3305)) +- add F2 support ([#3303](https://github.com/embassy-rs/embassy/pull/3303)) +- Add U5 support ([#2591](https://github.com/embassy-rs/embassy/pull/2591), [#2792](https://github.com/embassy-rs/embassy/pull/2792)) +- Add H50x support ([#2600](https://github.com/embassy-rs/embassy/pull/2600), [#2808](https://github.com/embassy-rs/embassy/pull/2808)) +- Fix flash erase on F3 ([#3744](https://github.com/embassy-rs/embassy/pull/3744)) +- Support G0 second flash bank ([#3711](https://github.com/embassy-rs/embassy/pull/3711)) +- F1, F3: wait for BSY flag to clear before flashing ([#3217](https://github.com/embassy-rs/embassy/pull/3217)) +- H7: enhance resilience to program sequence errors (pgserr) ([#2539](https://github.com/embassy-rs/embassy/pull/2539)) + +ADC: +- Add `AnyAdcChannel` type. You can obtain it from a pin with `.degrade_adc()`. Useful for making arrays of ADC pins. ([#2985](https://github.com/embassy-rs/embassy/pull/2985)) +- Add L0 support ([#2544](https://github.com/embassy-rs/embassy/pull/2544)) +- Add U5 support ([#3688](https://github.com/embassy-rs/embassy/pull/3688)) +- Add H5 support ([#2613](https://github.com/embassy-rs/embassy/pull/2613), [#3557](https://github.com/embassy-rs/embassy/pull/3557)) +- Add G4 async support ([#3566](https://github.com/embassy-rs/embassy/pull/3566)) +- Add G4 support for calibrating differential inputs ([#3735](https://github.com/embassy-rs/embassy/pull/3735)) +- Add oversampling and differential support for G4 ([#3169](https://github.com/embassy-rs/embassy/pull/3169)) +- Add DMA support for ADC v2 ([#3116](https://github.com/embassy-rs/embassy/pull/3116)) +- Add DMA support for ADC v3 and v4 ([#3128](https://github.com/embassy-rs/embassy/pull/3128)) +- Unify naming `blocking_read` for blocking, `read` for async. ([#3148](https://github.com/embassy-rs/embassy/pull/3148)) +- Fix channel count for the STM32G4 ADCs. ([#2828](https://github.com/embassy-rs/embassy/pull/2828)) +- Fix blocking_delay_us() overflowing when sys freq is high ([#2825](https://github.com/embassy-rs/embassy/pull/2825)) +- Remove need for taking a `Delay` impl. ([#2797](https://github.com/embassy-rs/embassy/pull/2797)) +- H5: set OR.OP0 to 1 when ADCx_INP0 is selected, per RM ([#2776](https://github.com/embassy-rs/embassy/pull/2776)) +- Add oversampling support ([#3124](https://github.com/embassy-rs/embassy/pull/3124)) +- Adc averaging support for ADC v4. ([#3110](https://github.com/embassy-rs/embassy/pull/3110)) +- F2 ADC fixes ([#2513](https://github.com/embassy-rs/embassy/pull/2513)) + +DAC: +- Fix new_internal not setting mode as documented ([#2886](https://github.com/embassy-rs/embassy/pull/2886)) + +OPAMP: +- Add missing opamp external outputs for STM32G4 ([#3636](https://github.com/embassy-rs/embassy/pull/3636)) +- Add extra lifetime to opamp-using structs ([#3207](https://github.com/embassy-rs/embassy/pull/3207)) +- Make OpAmp usable in follower configuration for internal DAC channel ([#3021](https://github.com/embassy-rs/embassy/pull/3021)) + +CAN: +- Add FDCAN support. ([#2475](https://github.com/embassy-rs/embassy/pull/2475), [#2571](https://github.com/embassy-rs/embassy/pull/2571), [#2623](https://github.com/embassy-rs/embassy/pull/2623), [#2631](https://github.com/embassy-rs/embassy/pull/2631), [#2635](https://github.com/embassy-rs/embassy/pull/2635), [#2637](https://github.com/embassy-rs/embassy/pull/2637), [#2645](https://github.com/embassy-rs/embassy/pull/2645), [#2647](https://github.com/embassy-rs/embassy/pull/2647), [#2658](https://github.com/embassy-rs/embassy/pull/2658), [#2703](https://github.com/embassy-rs/embassy/pull/2703), [#3364](https://github.com/embassy-rs/embassy/pull/3364)) +- Simplify BXCAN API, make BXCAN and FDCAN APIs consistent. ([#2760](https://github.com/embassy-rs/embassy/pull/2760), [#2693](https://github.com/embassy-rs/embassy/pull/2693), [#2744](https://github.com/embassy-rs/embassy/pull/2744)) +- Add buffered mode support ([#2588](https://github.com/embassy-rs/embassy/pull/2588)) +- Add support for modifying the receiver filters from `BufferedCan`, `CanRx`, and `BufferedCanRx` ([#3733](https://github.com/embassy-rs/embassy/pull/3733)) +- Add support for optional FIFO scheduling for outgoing frames ([#2988](https://github.com/embassy-rs/embassy/pull/2988)) +- fdcan: Properties for common runtime get/set operations ([#2840](https://github.com/embassy-rs/embassy/pull/2840)) +- fdcan: implement bus-off recovery ([#2832](https://github.com/embassy-rs/embassy/pull/2832)) +- Add BXCAN sleep/wakeup functionality ([#2854](https://github.com/embassy-rs/embassy/pull/2854)) +- Fix BXCAN hangs ([#3468](https://github.com/embassy-rs/embassy/pull/3468)) +- add RTR flag if it is remote frame ([#3421](https://github.com/embassy-rs/embassy/pull/3421)) +- Fix log storm when no CAN is connected ([#3284](https://github.com/embassy-rs/embassy/pull/3284)) +- Fix error handling ([#2850](https://github.com/embassy-rs/embassy/pull/2850)) +- Give CAN a kick when writing into TX buffer via sender. ([#2646](https://github.com/embassy-rs/embassy/pull/2646)) +- Preseve the RTR flag in messages. ([#2745](https://github.com/embassy-rs/embassy/pull/2745)) + +FMC: +- Add 13bit address sdram constructors ([#3189](https://github.com/embassy-rs/embassy/pull/3189)) + +xSPI: +- Add OCTOSPI support ([#2672](https://github.com/embassy-rs/embassy/pull/2672)) +- Add OCTOSPIM support ([#3102](https://github.com/embassy-rs/embassy/pull/3102)) +- Add HEXADECASPI support ([#3667](https://github.com/embassy-rs/embassy/pull/3667)) +- Add memory mapping support for QSPI ([#3725](https://github.com/embassy-rs/embassy/pull/3725)) +- Add memory mapping support for OCTOSPI ([#3456](https://github.com/embassy-rs/embassy/pull/3456)) +- Add async support for QSPI ([#3475](https://github.com/embassy-rs/embassy/pull/3475)) +- Fix QSPI synchronous read operation hangs when FIFO is not full ([#3724](https://github.com/embassy-rs/embassy/pull/3724)) +- Stick to `blocking_*` naming convention for QSPI, OSPI ([#3661](https://github.com/embassy-rs/embassy/pull/3661)) + +SDMMC: +- Add `block-device-driver` impl for use with `embedded-fatfs` ([#2607](https://github.com/embassy-rs/embassy/pull/2607)) +- Allow cmd block to be passed in for sdmmc dma transfers ([#3188](https://github.com/embassy-rs/embassy/pull/3188)) + +ETH: +- Fix reception of multicast packets ([#3488](https://github.com/embassy-rs/embassy/pull/3488), [#3707](https://github.com/embassy-rs/embassy/pull/3707)) +- Add support for executing custom SMI commands ([#3355](https://github.com/embassy-rs/embassy/pull/3355)) +- Add support for MII interface ([#2465](https://github.com/embassy-rs/embassy/pull/2465)) + +USB: +- Assert correct clock on init. ([#2711](https://github.com/embassy-rs/embassy/pull/2711)) +- Set PWR_CR2 USV on STM32L4 ([#2605](https://github.com/embassy-rs/embassy/pull/2605)) +- USBD driver improvements: + - Add ISO endpoint support ([#3314](https://github.com/embassy-rs/embassy/pull/3314)) + - Add support for L1. ([#2452](https://github.com/embassy-rs/embassy/pull/2452)) + - set USB initialization delay to 1µs ([#3700](https://github.com/embassy-rs/embassy/pull/3700)) +- OTG driver improvements: + - Add ISO endpoint support ([#3314](https://github.com/embassy-rs/embassy/pull/3314)) + - Add support for U595, U5A5 ([#3613](https://github.com/embassy-rs/embassy/pull/3613)) + - Add support for STM32H7R/S ([#3337](https://github.com/embassy-rs/embassy/pull/3337)) + - Add support for full-speed ULPI mode ([#3281](https://github.com/embassy-rs/embassy/pull/3281)) + - Make max EP count configurable ([#2881](https://github.com/embassy-rs/embassy/pull/2881)) + - fix corruption in CONTROL OUT transfers in stm32f4. ([#3565](https://github.com/embassy-rs/embassy/pull/3565)) + - Extract Synopsys USB OTG driver to a separate crate ([#2871](https://github.com/embassy-rs/embassy/pull/2871)) + - Add critical sections to avoid USB OTG corruption Errata ([#2823](https://github.com/embassy-rs/embassy/pull/2823)) + - Fix support for OTG_HS in FS mode. ([#2805](https://github.com/embassy-rs/embassy/pull/2805)) + +I2S: +- Add SPIv3 support. ([#2992](https://github.com/embassy-rs/embassy/pull/2992)) +- Add full-duplex support. ([#2992](https://github.com/embassy-rs/embassy/pull/2992)) +- Add I2S ringbuffered DMA support ([#3023](https://github.com/embassy-rs/embassy/pull/3023)) +- Fix STM32F4 I2S clock calculations ([#3716](https://github.com/embassy-rs/embassy/pull/3716)) + +SAI: +- Add a function that waits for any SAI/ringbuffer write error ([#3545](https://github.com/embassy-rs/embassy/pull/3545)) +- Disallow start without an initial write ([#3541](https://github.com/embassy-rs/embassy/pull/3541)) +- Flush FIFO on init and disable ([#3538](https://github.com/embassy-rs/embassy/pull/3538)) +- Fix MCKDIV for SAI v3/v4 ([#2710](https://github.com/embassy-rs/embassy/pull/2710)) +- Pull down clock and data lines in receive mode ([#3326](https://github.com/embassy-rs/embassy/pull/3326)) +- Add function to check if SAI is muted ([#3282](https://github.com/embassy-rs/embassy/pull/3282)) + +Low-power support: +- Update `embassy-executor` to v0.7. +- Add support for U0 ([#3556](https://github.com/embassy-rs/embassy/pull/3556)) +- Add support for U5 ([#3496](https://github.com/embassy-rs/embassy/pull/3496)) +- Add support for H5 ([#2877](https://github.com/embassy-rs/embassy/pull/2877)) +- Add support for L4 ([#3213](https://github.com/embassy-rs/embassy/pull/3213)) +- Fix low-power EXTI IRQ handler dropped edges ([#3404](https://github.com/embassy-rs/embassy/pull/3404)) +- Fix alarms not triggering in some cases ([#3592](https://github.com/embassy-rs/embassy/pull/3592)) + +Timer: +- Add Input Capture high-level driver ([#2912](https://github.com/embassy-rs/embassy/pull/2912)) +- Add PWM Input high-level driver ([#3014](https://github.com/embassy-rs/embassy/pull/3014)) +- Add support for splitting `SimplePwm` into channels ([#3317](https://github.com/embassy-rs/embassy/pull/3317)) +- Fix `SimplePwm` not enabling output pin in some stm32 families ([#2670](https://github.com/embassy-rs/embassy/pull/2670)) +- Add LPTIM low-level driver. ([#3310](https://github.com/embassy-rs/embassy/pull/3310)) +- Low-level TIM driver improvements: + - Simplify traits, convert from trait methods to struct. ([#2728](https://github.com/embassy-rs/embassy/pull/2728)) + - Add `low_level::Timer::get_clock_frequency()` ([#2908](https://github.com/embassy-rs/embassy/pull/2908)) + - Fix 32bit timer off by one ARR error ([#2876](https://github.com/embassy-rs/embassy/pull/2876)) + - Avoid max_compare_value >= u16::MAX ([#3549](https://github.com/embassy-rs/embassy/pull/3549)) + +DMA: +- Add `AnyChannel` type. Similar to `AnyPin`, it allows representing any DMA channel at runtime without needing generics. ([#2606](https://github.com/embassy-rs/embassy/pull/2606)) +, Add support for BDMA on H7 ([#2606](https://github.com/embassy-rs/embassy/pull/2606)) +- Add async `stop()` function to BDMA, DMA ([#2757](https://github.com/embassy-rs/embassy/pull/2757)) +- Add configuration option for DMA Request Priority ([#2680](https://github.com/embassy-rs/embassy/pull/2680)) +- Rewrite DMA ringbuffers ([#3336](https://github.com/embassy-rs/embassy/pull/3336)) +- Enable half transfer IRQ when constructing a ReadableDmaRingBuffer ([#3093](https://github.com/embassy-rs/embassy/pull/3093)) +- Right-align `write_immediate()` in ring buffers ([#3588](https://github.com/embassy-rs/embassy/pull/3588)) + +`embassy-time` driver: +- Update to `embassy-time` v0.4, `embassy-time-driver` v0.2. ([#3593](https://github.com/embassy-rs/embassy/pull/3593)) +- Change preference order of `time-driver-any` to pick less-featureful timers first. ([#2570](https://github.com/embassy-rs/embassy/pull/2570)) +- Allow using more TIMx timers for the time driver of TIM1 ([#2570](https://github.com/embassy-rs/embassy/pull/2570), [#2614](https://github.com/embassy-rs/embassy/pull/2614)) +- Correctly gate `time` feature of embassy-embedded-hal in embassy-stm32 ([#3359](https://github.com/embassy-rs/embassy/pull/3359)) +- adds timer-driver for tim21 and tim22 (on L0) ([#2450](https://github.com/embassy-rs/embassy/pull/2450)) + +WDG: +- Allow higher PSC value for iwdg_v3 ... ([#2628](https://github.com/embassy-rs/embassy/pull/2628)) + +Misc: +- Allow `bind_interrupts!` to accept conditional compilation attrs ([#3444](https://github.com/embassy-rs/embassy/pull/3444)) + +## 0.1.0 - 2024-01-12 + +First release. diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index ac9e9644c..034f51df9 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-stm32" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for ST STM32 series microcontrollers" @@ -19,16 +19,22 @@ flavors = [ { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi", features = ["low-power"] }, + { regex_feature = "stm32f4[2367]..[ig]", target = "thumbv7em-none-eabi", features = ["low-power", "dual-bank"] }, + { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi", features = ["low-power", "single-bank"] }, + { regex_feature = "stm32f7[67]..[ig]", target = "thumbv7em-none-eabi", features = ["dual-bank"] }, { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "stm32g0...c", target = "thumbv6m-none-eabi", features = ["dual-bank"] }, { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, + { regex_feature = "stm32g4[78].*", target = "thumbv7em-none-eabi", features = ["low-power", "dual-bank"] }, { regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi", features = ["low-power"] }, { regex_feature = "stm32h5.*", target = "thumbv8m.main-none-eabihf", features = ["low-power"] }, { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi", features = ["low-power"] }, { regex_feature = "stm32l1.*", target = "thumbv7m-none-eabi" }, + { regex_feature = "stm32l4[pqrs].*", target = "thumbv7em-none-eabi", features = ["dual-bank"] }, { regex_feature = "stm32l4.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32l5...e", target = "thumbv8m.main-none-eabihf", features = ["low-power", "dual-bank"] }, { regex_feature = "stm32l5.*", target = "thumbv8m.main-none-eabihf", features = ["low-power"] }, { regex_feature = "stm32u0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32u5.*", target = "thumbv8m.main-none-eabihf" }, @@ -38,20 +44,21 @@ flavors = [ ] [package.metadata.docs.rs] -features = ["defmt", "unstable-pac", "exti", "time-driver-any", "time", "stm32h755zi-cm7"] +features = ["defmt", "unstable-pac", "exti", "time-driver-any", "time", "stm32h755zi-cm7", "single-bank"] rustdoc-args = ["--cfg", "docsrs"] [dependencies] -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } -embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } -embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal", default-features = false } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } -embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } -embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" } -embassy-executor = { version = "0.6.0", path = "../embassy-executor", optional = true } +embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } +embassy-usb-synopsys-otg = { version = "0.2.0", path = "../embassy-usb-synopsys-otg" } +embassy-executor = { version = "0.7.0", path = "../embassy-executor", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } @@ -62,17 +69,19 @@ embedded-can = "0.4" embedded-storage = "0.3.1" embedded-storage-async = { version = "0.4.1" } +rand-core-06 = { package = "rand_core", version = "0.6" } +rand-core-09 = { package = "rand_core", version = "0.9" } -defmt = { version = "0.3", optional = true } + +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" futures-util = { version = "0.3.30", default-features = false } -rand_core = "0.6.3" -sdio-host = "0.5.0" +sdio-host = "0.9.0" critical-section = "1.1" -#stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e0cfd165fd8fffaa0df66a35eeca83b228496645" } +#stm32-metapac = { version = "16" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-27ef8fba3483187e852eaf3796d827259f61e8ec" } vcell = "0.1.3" nb = "1.0.0" @@ -88,16 +97,20 @@ static_assertions = { version = "1.1" } volatile-register = { version = "0.2.1" } bitflags = "2.4.2" +block-device-driver = { version = "0.2" } +aligned = "0.4.1" [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } +proptest = "1.5.0" +proptest-state-machine = "0.3.0" [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-e0cfd165fd8fffaa0df66a35eeca83b228496645", default-features = false, features = ["metadata"] } +#stm32-metapac = { version = "16", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-27ef8fba3483187e852eaf3796d827259f61e8ec", default-features = false, features = ["metadata"] } [features] default = ["rt"] @@ -106,14 +119,24 @@ default = ["rt"] rt = ["stm32-metapac/rt"] ## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging -defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-internal/defmt", "embedded-io-async/defmt-03", "embassy-usb-driver/defmt", "embassy-net-driver/defmt", "embassy-time?/defmt"] +defmt = [ + "dep:defmt", + "embassy-sync/defmt", + "embassy-embedded-hal/defmt", + "embassy-hal-internal/defmt", + "embedded-io-async/defmt-03", + "embassy-usb-driver/defmt", + "embassy-net-driver/defmt", + "embassy-time?/defmt", + "embassy-usb-synopsys-otg/defmt", +] exti = [] low-power = [ "dep:embassy-executor", "embassy-executor?/arch-cortex-m", "time" ] low-power-debug-with-sleep = [] -## Automatically generate `memory.x` file using [`stm32-metapac`](https://docs.rs/stm32-metapac/) -memory-x = ["stm32-metapac/memory-x"] +## Automatically generate `memory.x` file based on the memory map from [`stm32-metapac`](https://docs.rs/stm32-metapac/) +memory-x = [] ## Use secure registers when TrustZone is enabled trustzone-secure = [] @@ -124,14 +147,18 @@ trustzone-secure = [] ## There are no plans to make this stable. unstable-pac = [] +## Enable this feature to disable the overclocking check. +## DO NOT ENABLE THIS FEATURE UNLESS YOU KNOW WHAT YOU'RE DOING. +unchecked-overclocking = [] + #! ## Time ## Enables additional driver features that depend on embassy-time -time = ["dep:embassy-time"] +time = ["dep:embassy-time", "embassy-embedded-hal/time"] # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. -_time-driver = ["dep:embassy-time-driver", "time"] +_time-driver = ["dep:embassy-time-driver", "time", "dep:embassy-time-queue-utils"] ## Use any time driver time-driver-any = ["_time-driver"] @@ -178,11 +205,17 @@ split-pc2 = ["_split-pins-enabled"] ## Split PC3 split-pc3 = ["_split-pins-enabled"] +dual-bank = [] +single-bank = [] + ## internal use only _split-pins-enabled = [] ## internal use only _dual-core = [] +_core-cm0p = [] +_core-cm4 = [] +_core-cm7 = [] #! ## Chip-selection features #! Select your chip by specifying the model as a feature, e.g. `stm32c011d6`. @@ -1007,40 +1040,40 @@ stm32h743xg = [ "stm32-metapac/stm32h743xg" ] stm32h743xi = [ "stm32-metapac/stm32h743xi" ] stm32h743zg = [ "stm32-metapac/stm32h743zg" ] stm32h743zi = [ "stm32-metapac/stm32h743zi" ] -stm32h745bg-cm7 = [ "stm32-metapac/stm32h745bg-cm7", "_dual-core" ] -stm32h745bg-cm4 = [ "stm32-metapac/stm32h745bg-cm4", "_dual-core" ] -stm32h745bi-cm7 = [ "stm32-metapac/stm32h745bi-cm7", "_dual-core" ] -stm32h745bi-cm4 = [ "stm32-metapac/stm32h745bi-cm4", "_dual-core" ] -stm32h745ig-cm7 = [ "stm32-metapac/stm32h745ig-cm7", "_dual-core" ] -stm32h745ig-cm4 = [ "stm32-metapac/stm32h745ig-cm4", "_dual-core" ] -stm32h745ii-cm7 = [ "stm32-metapac/stm32h745ii-cm7", "_dual-core" ] -stm32h745ii-cm4 = [ "stm32-metapac/stm32h745ii-cm4", "_dual-core" ] -stm32h745xg-cm7 = [ "stm32-metapac/stm32h745xg-cm7", "_dual-core" ] -stm32h745xg-cm4 = [ "stm32-metapac/stm32h745xg-cm4", "_dual-core" ] -stm32h745xi-cm7 = [ "stm32-metapac/stm32h745xi-cm7", "_dual-core" ] -stm32h745xi-cm4 = [ "stm32-metapac/stm32h745xi-cm4", "_dual-core" ] -stm32h745zg-cm7 = [ "stm32-metapac/stm32h745zg-cm7", "_dual-core" ] -stm32h745zg-cm4 = [ "stm32-metapac/stm32h745zg-cm4", "_dual-core" ] -stm32h745zi-cm7 = [ "stm32-metapac/stm32h745zi-cm7", "_dual-core" ] -stm32h745zi-cm4 = [ "stm32-metapac/stm32h745zi-cm4", "_dual-core" ] -stm32h747ag-cm7 = [ "stm32-metapac/stm32h747ag-cm7", "_dual-core" ] -stm32h747ag-cm4 = [ "stm32-metapac/stm32h747ag-cm4", "_dual-core" ] -stm32h747ai-cm7 = [ "stm32-metapac/stm32h747ai-cm7", "_dual-core" ] -stm32h747ai-cm4 = [ "stm32-metapac/stm32h747ai-cm4", "_dual-core" ] -stm32h747bg-cm7 = [ "stm32-metapac/stm32h747bg-cm7", "_dual-core" ] -stm32h747bg-cm4 = [ "stm32-metapac/stm32h747bg-cm4", "_dual-core" ] -stm32h747bi-cm7 = [ "stm32-metapac/stm32h747bi-cm7", "_dual-core" ] -stm32h747bi-cm4 = [ "stm32-metapac/stm32h747bi-cm4", "_dual-core" ] -stm32h747ig-cm7 = [ "stm32-metapac/stm32h747ig-cm7", "_dual-core" ] -stm32h747ig-cm4 = [ "stm32-metapac/stm32h747ig-cm4", "_dual-core" ] -stm32h747ii-cm7 = [ "stm32-metapac/stm32h747ii-cm7", "_dual-core" ] -stm32h747ii-cm4 = [ "stm32-metapac/stm32h747ii-cm4", "_dual-core" ] -stm32h747xg-cm7 = [ "stm32-metapac/stm32h747xg-cm7", "_dual-core" ] -stm32h747xg-cm4 = [ "stm32-metapac/stm32h747xg-cm4", "_dual-core" ] -stm32h747xi-cm7 = [ "stm32-metapac/stm32h747xi-cm7", "_dual-core" ] -stm32h747xi-cm4 = [ "stm32-metapac/stm32h747xi-cm4", "_dual-core" ] -stm32h747zi-cm7 = [ "stm32-metapac/stm32h747zi-cm7", "_dual-core" ] -stm32h747zi-cm4 = [ "stm32-metapac/stm32h747zi-cm4", "_dual-core" ] +stm32h745bg-cm7 = [ "stm32-metapac/stm32h745bg-cm7", "_dual-core", "_core-cm7" ] +stm32h745bg-cm4 = [ "stm32-metapac/stm32h745bg-cm4", "_dual-core", "_core-cm4" ] +stm32h745bi-cm7 = [ "stm32-metapac/stm32h745bi-cm7", "_dual-core", "_core-cm7" ] +stm32h745bi-cm4 = [ "stm32-metapac/stm32h745bi-cm4", "_dual-core", "_core-cm4" ] +stm32h745ig-cm7 = [ "stm32-metapac/stm32h745ig-cm7", "_dual-core", "_core-cm7" ] +stm32h745ig-cm4 = [ "stm32-metapac/stm32h745ig-cm4", "_dual-core", "_core-cm4" ] +stm32h745ii-cm7 = [ "stm32-metapac/stm32h745ii-cm7", "_dual-core", "_core-cm7" ] +stm32h745ii-cm4 = [ "stm32-metapac/stm32h745ii-cm4", "_dual-core", "_core-cm4" ] +stm32h745xg-cm7 = [ "stm32-metapac/stm32h745xg-cm7", "_dual-core", "_core-cm7" ] +stm32h745xg-cm4 = [ "stm32-metapac/stm32h745xg-cm4", "_dual-core", "_core-cm4" ] +stm32h745xi-cm7 = [ "stm32-metapac/stm32h745xi-cm7", "_dual-core", "_core-cm7" ] +stm32h745xi-cm4 = [ "stm32-metapac/stm32h745xi-cm4", "_dual-core", "_core-cm4" ] +stm32h745zg-cm7 = [ "stm32-metapac/stm32h745zg-cm7", "_dual-core", "_core-cm7" ] +stm32h745zg-cm4 = [ "stm32-metapac/stm32h745zg-cm4", "_dual-core", "_core-cm4" ] +stm32h745zi-cm7 = [ "stm32-metapac/stm32h745zi-cm7", "_dual-core", "_core-cm7" ] +stm32h745zi-cm4 = [ "stm32-metapac/stm32h745zi-cm4", "_dual-core", "_core-cm4" ] +stm32h747ag-cm7 = [ "stm32-metapac/stm32h747ag-cm7", "_dual-core", "_core-cm7" ] +stm32h747ag-cm4 = [ "stm32-metapac/stm32h747ag-cm4", "_dual-core", "_core-cm4" ] +stm32h747ai-cm7 = [ "stm32-metapac/stm32h747ai-cm7", "_dual-core", "_core-cm7" ] +stm32h747ai-cm4 = [ "stm32-metapac/stm32h747ai-cm4", "_dual-core", "_core-cm4" ] +stm32h747bg-cm7 = [ "stm32-metapac/stm32h747bg-cm7", "_dual-core", "_core-cm7" ] +stm32h747bg-cm4 = [ "stm32-metapac/stm32h747bg-cm4", "_dual-core", "_core-cm4" ] +stm32h747bi-cm7 = [ "stm32-metapac/stm32h747bi-cm7", "_dual-core", "_core-cm7" ] +stm32h747bi-cm4 = [ "stm32-metapac/stm32h747bi-cm4", "_dual-core", "_core-cm4" ] +stm32h747ig-cm7 = [ "stm32-metapac/stm32h747ig-cm7", "_dual-core", "_core-cm7" ] +stm32h747ig-cm4 = [ "stm32-metapac/stm32h747ig-cm4", "_dual-core", "_core-cm4" ] +stm32h747ii-cm7 = [ "stm32-metapac/stm32h747ii-cm7", "_dual-core", "_core-cm7" ] +stm32h747ii-cm4 = [ "stm32-metapac/stm32h747ii-cm4", "_dual-core", "_core-cm4" ] +stm32h747xg-cm7 = [ "stm32-metapac/stm32h747xg-cm7", "_dual-core", "_core-cm7" ] +stm32h747xg-cm4 = [ "stm32-metapac/stm32h747xg-cm4", "_dual-core", "_core-cm4" ] +stm32h747xi-cm7 = [ "stm32-metapac/stm32h747xi-cm7", "_dual-core", "_core-cm7" ] +stm32h747xi-cm4 = [ "stm32-metapac/stm32h747xi-cm4", "_dual-core", "_core-cm4" ] +stm32h747zi-cm7 = [ "stm32-metapac/stm32h747zi-cm7", "_dual-core", "_core-cm7" ] +stm32h747zi-cm4 = [ "stm32-metapac/stm32h747zi-cm4", "_dual-core", "_core-cm4" ] stm32h750ib = [ "stm32-metapac/stm32h750ib" ] stm32h750vb = [ "stm32-metapac/stm32h750vb" ] stm32h750xb = [ "stm32-metapac/stm32h750xb" ] @@ -1051,24 +1084,24 @@ stm32h753ii = [ "stm32-metapac/stm32h753ii" ] stm32h753vi = [ "stm32-metapac/stm32h753vi" ] stm32h753xi = [ "stm32-metapac/stm32h753xi" ] stm32h753zi = [ "stm32-metapac/stm32h753zi" ] -stm32h755bi-cm7 = [ "stm32-metapac/stm32h755bi-cm7", "_dual-core" ] -stm32h755bi-cm4 = [ "stm32-metapac/stm32h755bi-cm4", "_dual-core" ] -stm32h755ii-cm7 = [ "stm32-metapac/stm32h755ii-cm7", "_dual-core" ] -stm32h755ii-cm4 = [ "stm32-metapac/stm32h755ii-cm4", "_dual-core" ] -stm32h755xi-cm7 = [ "stm32-metapac/stm32h755xi-cm7", "_dual-core" ] -stm32h755xi-cm4 = [ "stm32-metapac/stm32h755xi-cm4", "_dual-core" ] -stm32h755zi-cm7 = [ "stm32-metapac/stm32h755zi-cm7", "_dual-core" ] -stm32h755zi-cm4 = [ "stm32-metapac/stm32h755zi-cm4", "_dual-core" ] -stm32h757ai-cm7 = [ "stm32-metapac/stm32h757ai-cm7", "_dual-core" ] -stm32h757ai-cm4 = [ "stm32-metapac/stm32h757ai-cm4", "_dual-core" ] -stm32h757bi-cm7 = [ "stm32-metapac/stm32h757bi-cm7", "_dual-core" ] -stm32h757bi-cm4 = [ "stm32-metapac/stm32h757bi-cm4", "_dual-core" ] -stm32h757ii-cm7 = [ "stm32-metapac/stm32h757ii-cm7", "_dual-core" ] -stm32h757ii-cm4 = [ "stm32-metapac/stm32h757ii-cm4", "_dual-core" ] -stm32h757xi-cm7 = [ "stm32-metapac/stm32h757xi-cm7", "_dual-core" ] -stm32h757xi-cm4 = [ "stm32-metapac/stm32h757xi-cm4", "_dual-core" ] -stm32h757zi-cm7 = [ "stm32-metapac/stm32h757zi-cm7", "_dual-core" ] -stm32h757zi-cm4 = [ "stm32-metapac/stm32h757zi-cm4", "_dual-core" ] +stm32h755bi-cm7 = [ "stm32-metapac/stm32h755bi-cm7", "_dual-core", "_core-cm7" ] +stm32h755bi-cm4 = [ "stm32-metapac/stm32h755bi-cm4", "_dual-core", "_core-cm4" ] +stm32h755ii-cm7 = [ "stm32-metapac/stm32h755ii-cm7", "_dual-core", "_core-cm7" ] +stm32h755ii-cm4 = [ "stm32-metapac/stm32h755ii-cm4", "_dual-core", "_core-cm4" ] +stm32h755xi-cm7 = [ "stm32-metapac/stm32h755xi-cm7", "_dual-core", "_core-cm7" ] +stm32h755xi-cm4 = [ "stm32-metapac/stm32h755xi-cm4", "_dual-core", "_core-cm4" ] +stm32h755zi-cm7 = [ "stm32-metapac/stm32h755zi-cm7", "_dual-core", "_core-cm7" ] +stm32h755zi-cm4 = [ "stm32-metapac/stm32h755zi-cm4", "_dual-core", "_core-cm4" ] +stm32h757ai-cm7 = [ "stm32-metapac/stm32h757ai-cm7", "_dual-core", "_core-cm7" ] +stm32h757ai-cm4 = [ "stm32-metapac/stm32h757ai-cm4", "_dual-core", "_core-cm4" ] +stm32h757bi-cm7 = [ "stm32-metapac/stm32h757bi-cm7", "_dual-core", "_core-cm7" ] +stm32h757bi-cm4 = [ "stm32-metapac/stm32h757bi-cm4", "_dual-core", "_core-cm4" ] +stm32h757ii-cm7 = [ "stm32-metapac/stm32h757ii-cm7", "_dual-core", "_core-cm7" ] +stm32h757ii-cm4 = [ "stm32-metapac/stm32h757ii-cm4", "_dual-core", "_core-cm4" ] +stm32h757xi-cm7 = [ "stm32-metapac/stm32h757xi-cm7", "_dual-core", "_core-cm7" ] +stm32h757xi-cm4 = [ "stm32-metapac/stm32h757xi-cm4", "_dual-core", "_core-cm4" ] +stm32h757zi-cm7 = [ "stm32-metapac/stm32h757zi-cm7", "_dual-core", "_core-cm7" ] +stm32h757zi-cm4 = [ "stm32-metapac/stm32h757zi-cm4", "_dual-core", "_core-cm4" ] stm32h7a3ag = [ "stm32-metapac/stm32h7a3ag" ] stm32h7a3ai = [ "stm32-metapac/stm32h7a3ai" ] stm32h7a3ig = [ "stm32-metapac/stm32h7a3ig" ] @@ -1601,14 +1634,14 @@ stm32wba55he = [ "stm32-metapac/stm32wba55he" ] stm32wba55hg = [ "stm32-metapac/stm32wba55hg" ] stm32wba55ue = [ "stm32-metapac/stm32wba55ue" ] stm32wba55ug = [ "stm32-metapac/stm32wba55ug" ] -stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4", "_dual-core" ] -stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p", "_dual-core" ] -stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4", "_dual-core" ] -stm32wl54jc-cm0p = [ "stm32-metapac/stm32wl54jc-cm0p", "_dual-core" ] -stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4", "_dual-core" ] -stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p", "_dual-core" ] -stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4", "_dual-core" ] -stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p", "_dual-core" ] +stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4", "_dual-core", "_core-cm4" ] +stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p", "_dual-core", "_core-cm0p" ] +stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4", "_dual-core", "_core-cm4" ] +stm32wl54jc-cm0p = [ "stm32-metapac/stm32wl54jc-cm0p", "_dual-core", "_core-cm0p" ] +stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4", "_dual-core", "_core-cm4" ] +stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p", "_dual-core", "_core-cm0p" ] +stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4", "_dual-core", "_core-cm4" ] +stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p", "_dual-core", "_core-cm0p" ] stm32wle4c8 = [ "stm32-metapac/stm32wle4c8" ] stm32wle4cb = [ "stm32-metapac/stm32wle4cb" ] stm32wle4cc = [ "stm32-metapac/stm32wle4cc" ] diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index d8a7ea0e6..bb5ef53d7 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -9,8 +9,8 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use stm32_metapac::metadata::ir::BitOffset; use stm32_metapac::metadata::{ - MemoryRegionKind, PeripheralRccKernelClock, PeripheralRccRegister, PeripheralRegisters, StopMode, ALL_CHIPS, - ALL_PERIPHERAL_VERSIONS, METADATA, + MemoryRegion, MemoryRegionKind, PeripheralRccKernelClock, PeripheralRccRegister, PeripheralRegisters, StopMode, + ALL_CHIPS, ALL_PERIPHERAL_VERSIONS, METADATA, }; #[path = "./build_common.rs"] @@ -49,13 +49,64 @@ fn main() { } } + // ======== + // Select the memory variant to use + let memory = { + let single_bank_selected = env::var("CARGO_FEATURE_SINGLE_BANK").is_ok(); + let dual_bank_selected = env::var("CARGO_FEATURE_DUAL_BANK").is_ok(); + + let single_bank_memory = METADATA.memory.iter().find(|mem| { + mem.iter().any(|region| region.name.contains("BANK_1")) + && !mem.iter().any(|region| region.name.contains("BANK_2")) + }); + + let dual_bank_memory = METADATA.memory.iter().find(|mem| { + mem.iter().any(|region| region.name.contains("BANK_1")) + && mem.iter().any(|region| region.name.contains("BANK_2")) + }); + + cfgs.set( + "bank_setup_configurable", + single_bank_memory.is_some() && dual_bank_memory.is_some(), + ); + + match (single_bank_selected, dual_bank_selected) { + (true, true) => panic!("Both 'single-bank' and 'dual-bank' features enabled"), + (true, false) => { + single_bank_memory.expect("The 'single-bank' feature is not supported on this dual bank chip") + } + (false, true) => { + dual_bank_memory.expect("The 'dual-bank' feature is not supported on this single bank chip") + } + (false, false) => { + if METADATA.memory.len() != 1 { + panic!("Chip supports single and dual bank configuration. No Cargo feature to select one is enabled. Use the 'single-bank' or 'dual-bank' feature to make your selection") + } + METADATA.memory[0] + } + } + }; + // ======== // Generate singletons let mut singletons: Vec = Vec::new(); + + // Generate one singleton per pin + for p in METADATA.pins { + singletons.push(p.name.to_string()); + } + + // generate one singleton per peripheral (with many exceptions...) for p in METADATA.peripherals { if let Some(r) = &p.registers { - if r.kind == "adccommon" || r.kind == "sai" || r.kind == "ucpd" { + if r.kind == "adccommon" + || r.kind == "sai" + || r.kind == "ucpd" + || r.kind == "otg" + || r.kind == "octospi" + || r.kind == "xspi" + { // TODO: should we emit this for all peripherals? if so, we will need a list of all // possible peripherals across all chips, so that we can declare the configs // (replacing the hard-coded list of `peri_*` cfgs below) @@ -63,13 +114,8 @@ fn main() { } match r.kind { - // Generate singletons per pin, not per port - "gpio" => { - let port_letter = p.name.strip_prefix("GPIO").unwrap(); - for pin_num in 0..16 { - singletons.push(format!("P{}{}", port_letter, pin_num)); - } - } + // handled above + "gpio" => {} // No singleton for these, the HAL handles them specially. "exti" => {} @@ -111,6 +157,10 @@ fn main() { "peri_sai4", "peri_ucpd1", "peri_ucpd2", + "peri_usb_otg_fs", + "peri_usb_otg_hs", + "peri_octospi2", + "peri_xspi2", ]); cfgs.declare_all(&["mco", "mco1", "mco2"]); @@ -192,7 +242,7 @@ fn main() { .to_ascii_lowercase(), ), Err(GetOneError::None) => None, - Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), + Err(GetOneError::Multiple) => panic!("Multiple time-driver-xxx Cargo features enabled"), }; let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) { @@ -278,8 +328,7 @@ fn main() { // ======== // Generate FLASH regions let mut flash_regions = TokenStream::new(); - let flash_memory_regions: Vec<_> = METADATA - .memory + let flash_memory_regions: Vec<_> = memory .iter() .filter(|x| x.kind == MemoryRegionKind::Flash && x.settings.is_some()) .collect(); @@ -291,6 +340,8 @@ fn main() { "Bank1" } else if region.name.starts_with("BANK_2") { "Bank2" + } else if region.name == "OTP" { + "Otp" } else { continue; } @@ -317,7 +368,7 @@ fn main() { let region_type = format_ident!("{}", get_flash_region_type_name(region.name)); flash_regions.extend(quote! { #[cfg(flash)] - pub struct #region_type<'d, MODE = crate::flash::Async>(pub &'static crate::flash::FlashRegion, pub(crate) embassy_hal_internal::PeripheralRef<'d, crate::peripherals::FLASH>, pub(crate) core::marker::PhantomData); + pub struct #region_type<'d, MODE = crate::flash::Async>(pub &'static crate::flash::FlashRegion, pub(crate) embassy_hal_internal::Peri<'d, crate::peripherals::FLASH>, pub(crate) core::marker::PhantomData); }); } @@ -349,7 +400,7 @@ fn main() { #[cfg(flash)] impl<'d, MODE> FlashLayout<'d, MODE> { - pub(crate) fn new(p: embassy_hal_internal::PeripheralRef<'d, crate::peripherals::FLASH>) -> Self { + pub(crate) fn new(p: embassy_hal_internal::Peri<'d, crate::peripherals::FLASH>) -> Self { Self { #(#inits),*, _mode: core::marker::PhantomData, @@ -475,9 +526,39 @@ fn main() { } impl<'a> ClockGen<'a> { + fn parse_mul_div(name: &str) -> (&str, Frac) { + if name == "hse_div_rtcpre" { + return (name, Frac { num: 1, denom: 1 }); + } + + if let Some(i) = name.find("_div_") { + let n = &name[..i]; + let val: u32 = name[i + 5..].parse().unwrap(); + (n, Frac { num: 1, denom: val }) + } else if let Some(i) = name.find("_mul_") { + let n = &name[..i]; + let val: u32 = name[i + 5..].parse().unwrap(); + (n, Frac { num: val, denom: 1 }) + } else { + (name, Frac { num: 1, denom: 1 }) + } + } + fn gen_clock(&mut self, peripheral: &str, name: &str) -> TokenStream { - let clock_name = format_ident!("{}", name.to_ascii_lowercase()); - self.clock_names.insert(name.to_ascii_lowercase()); + let name = name.to_ascii_lowercase(); + let (name, frac) = Self::parse_mul_div(&name); + let clock_name = format_ident!("{}", name); + self.clock_names.insert(name.to_string()); + + let mut muldiv = quote!(); + if frac.num != 1 { + let val = frac.num; + muldiv.extend(quote!(* #val)); + } + if frac.denom != 1 { + let val = frac.denom; + muldiv.extend(quote!(/ #val)); + } quote!(unsafe { unwrap!( crate::rcc::get_freqs().#clock_name.to_hertz(), @@ -486,6 +567,7 @@ fn main() { #peripheral, #name ) + #muldiv }) } @@ -527,7 +609,11 @@ fn main() { match crate::pac::RCC.#fieldset_name().read().#field_name() { #match_arms #[allow(unreachable_patterns)] - _ => unreachable!(), + _ => panic!( + "attempted to use peripheral '{}' but its clock mux is not set to a valid \ + clock. Change 'config.rcc.mux' to another clock.", + #peripheral + ) } } } @@ -598,6 +684,8 @@ fn main() { PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(p.name, clock), }; + let bus_clock_frequency = clock_gen.gen_clock(p.name, &rcc.bus_clock); + // 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_mode = match rcc.stop_mode { @@ -611,6 +699,9 @@ fn main() { fn frequency() -> crate::time::Hertz { #clock_frequency } + fn bus_frequency() -> crate::time::Hertz { + #bus_clock_frequency + } const RCC_INFO: crate::rcc::RccInfo = unsafe { crate::rcc::RccInfo::new( @@ -709,6 +800,16 @@ fn main() { // Generate RCC clock_gen.clock_names.insert("sys".to_string()); clock_gen.clock_names.insert("rtc".to_string()); + + // STM32F4 SPI in I2S mode receives a clock input from the dedicated I2S PLL. + // For this, there is an additional clock MUX, which is not present in other + // peripherals and does not fit the current RCC structure of stm32-data. + if chip_name.starts_with("stm32f4") && !chip_name.starts_with("stm32f410") { + clock_gen.clock_names.insert("plli2s1_p".to_string()); + clock_gen.clock_names.insert("plli2s1_q".to_string()); + clock_gen.clock_names.insert("plli2s1_r".to_string()); + } + let clock_idents: Vec<_> = clock_gen.clock_names.iter().map(|n| format_ident!("{}", n)).collect(); g.extend(quote! { #[derive(Clone, Copy, Debug)] @@ -857,6 +958,7 @@ fn main() { (("ltdc", "B7"), quote!(crate::ltdc::B7Pin)), (("usb", "DP"), quote!(crate::usb::DpPin)), (("usb", "DM"), quote!(crate::usb::DmPin)), + (("usb", "SOF"), quote!(crate::usb::SofPin)), (("otg", "DP"), quote!(crate::usb::DpPin)), (("otg", "DM"), quote!(crate::usb::DmPin)), (("otg", "ULPI_CK"), quote!(crate::usb::UlpiClkPin)), @@ -1015,6 +1117,9 @@ fn main() { (("hrtim", "CHE2"), quote!(crate::hrtim::ChannelEComplementaryPin)), (("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)), (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), + (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), + (("lptim", "CH2"), quote!(crate::lptim::Channel2Pin)), + (("lptim", "OUT"), quote!(crate::lptim::OutputPin)), (("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)), (("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)), (("sdmmc", "D0"), quote!(crate::sdmmc::D0Pin)), @@ -1024,7 +1129,7 @@ fn main() { (("sdmmc", "D4"), quote!(crate::sdmmc::D4Pin)), (("sdmmc", "D5"), quote!(crate::sdmmc::D5Pin)), (("sdmmc", "D6"), quote!(crate::sdmmc::D6Pin)), - (("sdmmc", "D6"), quote!(crate::sdmmc::D7Pin)), + (("sdmmc", "D7"), quote!(crate::sdmmc::D7Pin)), (("sdmmc", "D8"), quote!(crate::sdmmc::D8Pin)), (("quadspi", "BK1_IO0"), quote!(crate::qspi::BK1D0Pin)), (("quadspi", "BK1_IO1"), quote!(crate::qspi::BK1D1Pin)), @@ -1049,6 +1154,117 @@ fn main() { (("octospi", "NCS"), quote!(crate::ospi::NSSPin)), (("octospi", "CLK"), quote!(crate::ospi::SckPin)), (("octospi", "NCLK"), quote!(crate::ospi::NckPin)), + (("octospim", "P1_IO0"), quote!(crate::ospi::D0Pin)), + (("octospim", "P1_IO1"), quote!(crate::ospi::D1Pin)), + (("octospim", "P1_IO2"), quote!(crate::ospi::D2Pin)), + (("octospim", "P1_IO3"), quote!(crate::ospi::D3Pin)), + (("octospim", "P1_IO4"), quote!(crate::ospi::D4Pin)), + (("octospim", "P1_IO5"), quote!(crate::ospi::D5Pin)), + (("octospim", "P1_IO6"), quote!(crate::ospi::D6Pin)), + (("octospim", "P1_IO7"), quote!(crate::ospi::D7Pin)), + (("octospim", "P1_DQS"), quote!(crate::ospi::DQSPin)), + (("octospim", "P1_NCS"), quote!(crate::ospi::NSSPin)), + (("octospim", "P1_CLK"), quote!(crate::ospi::SckPin)), + (("octospim", "P1_NCLK"), quote!(crate::ospi::NckPin)), + (("octospim", "P2_IO0"), quote!(crate::ospi::D0Pin)), + (("octospim", "P2_IO1"), quote!(crate::ospi::D1Pin)), + (("octospim", "P2_IO2"), quote!(crate::ospi::D2Pin)), + (("octospim", "P2_IO3"), quote!(crate::ospi::D3Pin)), + (("octospim", "P2_IO4"), quote!(crate::ospi::D4Pin)), + (("octospim", "P2_IO5"), quote!(crate::ospi::D5Pin)), + (("octospim", "P2_IO6"), quote!(crate::ospi::D6Pin)), + (("octospim", "P2_IO7"), quote!(crate::ospi::D7Pin)), + (("octospim", "P2_DQS"), quote!(crate::ospi::DQSPin)), + (("octospim", "P2_NCS"), quote!(crate::ospi::NSSPin)), + (("octospim", "P2_CLK"), quote!(crate::ospi::SckPin)), + (("octospim", "P2_NCLK"), quote!(crate::ospi::NckPin)), + (("xspi", "IO0"), quote!(crate::xspi::D0Pin)), + (("xspi", "IO1"), quote!(crate::xspi::D1Pin)), + (("xspi", "IO2"), quote!(crate::xspi::D2Pin)), + (("xspi", "IO3"), quote!(crate::xspi::D3Pin)), + (("xspi", "IO4"), quote!(crate::xspi::D4Pin)), + (("xspi", "IO5"), quote!(crate::xspi::D5Pin)), + (("xspi", "IO6"), quote!(crate::xspi::D6Pin)), + (("xspi", "IO7"), quote!(crate::xspi::D7Pin)), + (("xspi", "IO8"), quote!(crate::xspi::D8Pin)), + (("xspi", "IO9"), quote!(crate::xspi::D9Pin)), + (("xspi", "IO10"), quote!(crate::xspi::D10Pin)), + (("xspi", "IO11"), quote!(crate::xspi::D11Pin)), + (("xspi", "IO12"), quote!(crate::xspi::D12Pin)), + (("xspi", "IO13"), quote!(crate::xspi::D13Pin)), + (("xspi", "IO14"), quote!(crate::xspi::D14Pin)), + (("xspi", "IO15"), quote!(crate::xspi::D15Pin)), + (("xspi", "DQS0"), quote!(crate::xspi::DQS0Pin)), + (("xspi", "DQS1"), quote!(crate::xspi::DQS1Pin)), + (("xspi", "NCS1"), quote!(crate::xspi::NCSPin)), + (("xspi", "NCS2"), quote!(crate::xspi::NCSPin)), + (("xspi", "CLK"), quote!(crate::xspi::CLKPin)), + (("xspi", "NCLK"), quote!(crate::xspi::NCLKPin)), + (("xspim", "P1_IO0"), quote!(crate::xspi::D0Pin)), + (("xspim", "P1_IO1"), quote!(crate::xspi::D1Pin)), + (("xspim", "P1_IO2"), quote!(crate::xspi::D2Pin)), + (("xspim", "P1_IO3"), quote!(crate::xspi::D3Pin)), + (("xspim", "P1_IO4"), quote!(crate::xspi::D4Pin)), + (("xspim", "P1_IO5"), quote!(crate::xspi::D5Pin)), + (("xspim", "P1_IO6"), quote!(crate::xspi::D6Pin)), + (("xspim", "P1_IO7"), quote!(crate::xspi::D7Pin)), + (("xspim", "P1_IO8"), quote!(crate::xspi::D8Pin)), + (("xspim", "P1_IO9"), quote!(crate::xspi::D9Pin)), + (("xspim", "P1_IO10"), quote!(crate::xspi::D10Pin)), + (("xspim", "P1_IO11"), quote!(crate::xspi::D11Pin)), + (("xspim", "P1_IO12"), quote!(crate::xspi::D12Pin)), + (("xspim", "P1_IO13"), quote!(crate::xspi::D13Pin)), + (("xspim", "P1_IO14"), quote!(crate::xspi::D14Pin)), + (("xspim", "P1_IO15"), quote!(crate::xspi::D15Pin)), + (("xspim", "P1_DQS0"), quote!(crate::xspi::DQS0Pin)), + (("xspim", "P1_DQS1"), quote!(crate::xspi::DQS1Pin)), + (("xspim", "P1_NCS1"), quote!(crate::xspi::NCSPin)), + (("xspim", "P1_NCS2"), quote!(crate::xspi::NCSPin)), + (("xspim", "P1_CLK"), quote!(crate::xspi::CLKPin)), + (("xspim", "P1_NCLK"), quote!(crate::xspi::NCLKPin)), + (("xspim", "P2_IO0"), quote!(crate::xspi::D0Pin)), + (("xspim", "P2_IO1"), quote!(crate::xspi::D1Pin)), + (("xspim", "P2_IO2"), quote!(crate::xspi::D2Pin)), + (("xspim", "P2_IO3"), quote!(crate::xspi::D3Pin)), + (("xspim", "P2_IO4"), quote!(crate::xspi::D4Pin)), + (("xspim", "P2_IO5"), quote!(crate::xspi::D5Pin)), + (("xspim", "P2_IO6"), quote!(crate::xspi::D6Pin)), + (("xspim", "P2_IO7"), quote!(crate::xspi::D7Pin)), + (("xspim", "P2_IO8"), quote!(crate::xspi::D8Pin)), + (("xspim", "P2_IO9"), quote!(crate::xspi::D9Pin)), + (("xspim", "P2_IO10"), quote!(crate::xspi::D10Pin)), + (("xspim", "P2_IO11"), quote!(crate::xspi::D11Pin)), + (("xspim", "P2_IO12"), quote!(crate::xspi::D12Pin)), + (("xspim", "P2_IO13"), quote!(crate::xspi::D13Pin)), + (("xspim", "P2_IO14"), quote!(crate::xspi::D14Pin)), + (("xspim", "P2_IO15"), quote!(crate::xspi::D15Pin)), + (("xspim", "P2_DQS0"), quote!(crate::xspi::DQS0Pin)), + (("xspim", "P2_DQS1"), quote!(crate::xspi::DQS1Pin)), + (("xspim", "P2_NCS1"), quote!(crate::xspi::NCSPin)), + (("xspim", "P2_NCS2"), quote!(crate::xspi::NCSPin)), + (("xspim", "P2_CLK"), quote!(crate::xspi::CLKPin)), + (("xspim", "P2_NCLK"), quote!(crate::xspi::NCLKPin)), + (("hspi", "IO0"), quote!(crate::hspi::D0Pin)), + (("hspi", "IO1"), quote!(crate::hspi::D1Pin)), + (("hspi", "IO2"), quote!(crate::hspi::D2Pin)), + (("hspi", "IO3"), quote!(crate::hspi::D3Pin)), + (("hspi", "IO4"), quote!(crate::hspi::D4Pin)), + (("hspi", "IO5"), quote!(crate::hspi::D5Pin)), + (("hspi", "IO6"), quote!(crate::hspi::D6Pin)), + (("hspi", "IO7"), quote!(crate::hspi::D7Pin)), + (("hspi", "IO8"), quote!(crate::hspi::D8Pin)), + (("hspi", "IO9"), quote!(crate::hspi::D9Pin)), + (("hspi", "IO10"), quote!(crate::hspi::D10Pin)), + (("hspi", "IO11"), quote!(crate::hspi::D11Pin)), + (("hspi", "IO12"), quote!(crate::hspi::D12Pin)), + (("hspi", "IO13"), quote!(crate::hspi::D13Pin)), + (("hspi", "IO14"), quote!(crate::hspi::D14Pin)), + (("hspi", "IO15"), quote!(crate::hspi::D15Pin)), + (("hspi", "DQS0"), quote!(crate::hspi::DQS0Pin)), + (("hspi", "DQS1"), quote!(crate::hspi::DQS1Pin)), + (("hspi", "NCS"), quote!(crate::hspi::NSSPin)), + (("hspi", "CLK"), quote!(crate::hspi::SckPin)), + (("hspi", "NCLK"), quote!(crate::hspi::NckPin)), (("tsc", "G1_IO1"), quote!(crate::tsc::G1IO1Pin)), (("tsc", "G1_IO2"), quote!(crate::tsc::G1IO2Pin)), (("tsc", "G1_IO3"), quote!(crate::tsc::G1IO3Pin)), @@ -1081,6 +1297,8 @@ fn main() { (("tsc", "G8_IO2"), quote!(crate::tsc::G8IO2Pin)), (("tsc", "G8_IO3"), quote!(crate::tsc::G8IO3Pin)), (("tsc", "G8_IO4"), quote!(crate::tsc::G8IO4Pin)), + (("dac", "OUT1"), quote!(crate::dac::DacPin)), + (("dac", "OUT2"), quote!(crate::dac::DacPin)), ].into(); for p in METADATA.peripherals { @@ -1106,6 +1324,41 @@ fn main() { peri = format_ident!("{}", pin.signal.replace('_', "")); } + // OCTOSPIM is special + if p.name == "OCTOSPIM" { + // Some chips have OCTOSPIM but not OCTOSPI2. + if METADATA.peripherals.iter().any(|p| p.name == "OCTOSPI2") { + peri = format_ident!("{}", "OCTOSPI2"); + g.extend(quote! { + pin_trait_impl!(#tr, #peri, #pin_name, #af); + }); + } + peri = format_ident!("{}", "OCTOSPI1"); + } + + // XSPIM is special + if p.name == "XSPIM" { + if pin.signal.starts_with("P1") { + peri = format_ident!("{}", "XSPI1"); + } else if pin.signal.starts_with("P2") { + peri = format_ident!("{}", "XSPI2"); + } else { + panic! {"malformed XSPIM pin: {:?}", pin} + } + } + + // XSPI NCS pin to CSSEL mapping + if pin.signal.ends_with("NCS1") { + g.extend(quote! { + sel_trait_impl!(crate::xspi::NCSEither, #peri, #pin_name, 0); + }) + } + if pin.signal.ends_with("NCS2") { + g.extend(quote! { + sel_trait_impl!(crate::xspi::NCSEither, #peri, #pin_name, 1); + }) + } + g.extend(quote! { pin_trait_impl!(#tr, #peri, #pin_name, #af); }) @@ -1158,6 +1411,18 @@ fn main() { g.extend(quote! { impl_opamp_vp_pin!( #peri, #pin_name, #ch); }) + } else if pin.signal.starts_with("VINM") { + // Impl NonInvertingPin for the VINM* signals ( VINM0, VINM1, etc) + // STM32G4 + let peri = format_ident!("{}", p.name); + let pin_name = format_ident!("{}", pin.pin); + let ch: Result = pin.signal.strip_prefix("VINM").unwrap().parse(); + + if let Ok(ch) = ch { + g.extend(quote! { + impl_opamp_vn_pin!( #peri, #pin_name, #ch); + }) + } } else if pin.signal == "VOUT" { // Impl OutputPin for the VOUT pin let peri = format_ident!("{}", p.name); @@ -1168,14 +1433,14 @@ fn main() { } } - // DAC is special - if regs.kind == "dac" { + if regs.kind == "spdifrx" { let peri = format_ident!("{}", p.name); let pin_name = format_ident!("{}", pin.pin); - let ch: u8 = pin.signal.strip_prefix("OUT").unwrap().parse().unwrap(); + let af = pin.af.unwrap_or(0); + let sel: u8 = pin.signal.strip_prefix("IN").unwrap().parse().unwrap(); g.extend(quote! { - impl_dac_pin!( #peri, #pin_name, #ch); + impl_spdifrx_pin!( #peri, #pin_name, #af, #sel); }) } } @@ -1185,13 +1450,12 @@ fn main() { // ======== // Generate dma_trait_impl! - let signals: HashMap<_, _> = [ + let mut signals: HashMap<_, _> = [ // (kind, signal) => trait (("adc", "ADC"), quote!(crate::adc::RxDma)), (("adc", "ADC1"), quote!(crate::adc::RxDma)), (("adc", "ADC2"), quote!(crate::adc::RxDma)), (("adc", "ADC3"), quote!(crate::adc::RxDma)), - (("adc", "ADC4"), quote!(crate::adc::RxDma)), (("ucpd", "RX"), quote!(crate::ucpd::RxDma)), (("ucpd", "TX"), quote!(crate::ucpd::TxDma)), (("usart", "RX"), quote!(crate::usart::RxDma)), @@ -1202,6 +1466,7 @@ fn main() { (("sai", "B"), quote!(crate::sai::Dma)), (("spi", "RX"), quote!(crate::spi::RxDma)), (("spi", "TX"), quote!(crate::spi::TxDma)), + (("spdifrx", "RX"), quote!(crate::spdifrx::Dma)), (("i2c", "RX"), quote!(crate::i2c::RxDma)), (("i2c", "TX"), quote!(crate::i2c::TxDma)), (("dcmi", "DCMI"), quote!(crate::dcmi::FrameDma)), @@ -1210,8 +1475,9 @@ fn main() { (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), (("octospi", "OCTOSPI1"), quote!(crate::ospi::OctoDma)), - (("dac", "CH1"), quote!(crate::dac::DacDma1)), - (("dac", "CH2"), quote!(crate::dac::DacDma2)), + (("hspi", "HSPI1"), quote!(crate::hspi::HspiDma)), + (("dac", "CH1"), quote!(crate::dac::Dma)), + (("dac", "CH2"), quote!(crate::dac::Dma)), (("timer", "UP"), quote!(crate::timer::UpDma)), (("hash", "IN"), quote!(crate::hash::Dma)), (("cryp", "IN"), quote!(crate::cryp::DmaIn)), @@ -1225,6 +1491,19 @@ fn main() { ] .into(); + if chip_name.starts_with("stm32u5") { + signals.insert(("adc", "ADC4"), quote!(crate::adc::RxDma4)); + } else { + signals.insert(("adc", "ADC4"), quote!(crate::adc::RxDma)); + } + + if chip_name.starts_with("stm32g4") { + let line_number = chip_name.chars().skip(8).next().unwrap(); + if line_number == '3' || line_number == '4' { + signals.insert(("adc", "ADC5"), quote!(crate::adc::RxDma)); + } + } + for p in METADATA.peripherals { if let Some(regs) = &p.registers { // FIXME: stm32u5a crash on Cordic driver @@ -1290,36 +1569,13 @@ fn main() { for e in rcc_registers.ir.enums { fn is_rcc_name(e: &str) -> bool { match e { - "Pllp" | "Pllq" | "Pllr" | "Pllm" | "Plln" => true, + "Pllp" | "Pllq" | "Pllr" | "Pllm" | "Plln" | "Prediv1" | "Prediv2" => true, "Timpre" | "Pllrclkpre" => false, e if e.ends_with("pre") || e.ends_with("pres") || e.ends_with("div") || e.ends_with("mul") => true, _ => false, } } - #[derive(Copy, Clone, Debug)] - struct Frac { - num: u32, - denom: u32, - } - - impl Frac { - fn simplify(self) -> Self { - let d = gcd(self.num, self.denom); - Self { - num: self.num / d, - denom: self.denom / d, - } - } - } - - fn gcd(a: u32, b: u32) -> u32 { - if b == 0 { - return a; - } - gcd(b, a % b) - } - fn parse_num(n: &str) -> Result { for prefix in ["DIV", "MUL"] { if let Some(n) = n.strip_prefix(prefix) { @@ -1402,8 +1658,7 @@ fn main() { let mut pins_table: Vec> = Vec::new(); let mut adc_table: Vec> = Vec::new(); - for m in METADATA - .memory + for m in memory .iter() .filter(|m| m.kind == MemoryRegionKind::Flash && m.settings.is_some()) { @@ -1419,43 +1674,42 @@ fn main() { let gpio_base = METADATA.peripherals.iter().find(|p| p.name == "GPIOA").unwrap().address as u32; let gpio_stride = 0x400; + for pin in METADATA.pins { + let port_letter = pin.name.chars().nth(1).unwrap(); + let pname = format!("GPIO{}", port_letter); + let p = METADATA.peripherals.iter().find(|p| p.name == pname).unwrap(); + assert_eq!(0, (p.address as u32 - gpio_base) % gpio_stride); + let port_num = (p.address as u32 - gpio_base) / gpio_stride; + let pin_num: u32 = pin.name[2..].parse().unwrap(); + + pins_table.push(vec![ + pin.name.to_string(), + p.name.to_string(), + port_num.to_string(), + pin_num.to_string(), + format!("EXTI{}", pin_num), + ]); + + // If we have the split pins, we need to do a little extra work: + // Add the "_C" variant to the table. The solution is not optimal, though. + // Adding them only when the corresponding GPIOx also appears. + // This should avoid unintended side-effects as much as possible. + #[cfg(feature = "_split-pins-enabled")] + for split_feature in &split_features { + if split_feature.pin_name_without_c == pin.name { + pins_table.push(vec![ + split_feature.pin_name_with_c.to_string(), + p.name.to_string(), + port_num.to_string(), + pin_num.to_string(), + format!("EXTI{}", pin_num), + ]); + } + } + } + for p in METADATA.peripherals { if let Some(regs) = &p.registers { - if regs.kind == "gpio" { - let port_letter = p.name.chars().nth(4).unwrap(); - assert_eq!(0, (p.address as u32 - gpio_base) % gpio_stride); - let port_num = (p.address as u32 - gpio_base) / gpio_stride; - - for pin_num in 0u32..16 { - let pin_name = format!("P{}{}", port_letter, pin_num); - - pins_table.push(vec![ - pin_name.clone(), - p.name.to_string(), - port_num.to_string(), - pin_num.to_string(), - format!("EXTI{}", pin_num), - ]); - - // If we have the split pins, we need to do a little extra work: - // Add the "_C" variant to the table. The solution is not optimal, though. - // Adding them only when the corresponding GPIOx also appears. - // This should avoid unintended side-effects as much as possible. - #[cfg(feature = "_split-pins-enabled")] - for split_feature in &split_features { - if split_feature.pin_name_without_c == pin_name { - pins_table.push(vec![ - split_feature.pin_name_with_c.to_string(), - p.name.to_string(), - port_num.to_string(), - pin_num.to_string(), - format!("EXTI{}", pin_num), - ]); - } - } - } - } - if regs.kind == "adc" { let adc_num = p.name.strip_prefix("ADC").unwrap(); let mut adc_common = None; @@ -1494,6 +1748,36 @@ fn main() { .flat_map(|p| &p.registers) .any(|p| p.kind == "dmamux"); + let mut dma_irqs: BTreeMap<&str, Vec> = BTreeMap::new(); + + for p in METADATA.peripherals { + if let Some(r) = &p.registers { + if r.kind == "dma" || r.kind == "bdma" || r.kind == "gpdma" || r.kind == "lpdma" { + 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); + } + } + } + } + + #[cfg(feature = "_dual-core")] + let mut dma_ch_to_irq: BTreeMap<&str, Vec> = BTreeMap::new(); + + #[cfg(feature = "_dual-core")] + for (irq, channels) in &dma_irqs { + for channel in channels { + dma_ch_to_irq.entry(channel).or_default().push(irq.to_string()); + } + } + 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() { @@ -1502,6 +1786,16 @@ fn main() { let name = format_ident!("{}", ch.name); let idx = ch_idx as u8; + #[cfg(feature = "_dual-core")] + let irq = { + let irq_name = if let Some(x) = &dma_ch_to_irq.get(ch.name) { + format_ident!("{}", x.get(0).unwrap()) + } else { + panic!("failed to find dma interrupt") + }; + quote!(crate::pac::Interrupt::#irq_name) + }; + g.extend(quote!(dma_channel_impl!(#name, #idx);)); let dma = format_ident!("{}", ch.dma); @@ -1532,6 +1826,7 @@ fn main() { None => quote!(), }; + #[cfg(not(feature = "_dual-core"))] dmas.extend(quote! { crate::dma::ChannelInfo { dma: #dma_info, @@ -1539,31 +1834,20 @@ fn main() { #dmamux }, }); + #[cfg(feature = "_dual-core")] + dmas.extend(quote! { + crate::dma::ChannelInfo { + dma: #dma_info, + num: #ch_num, + irq: #irq, + #dmamux + }, + }); } // ======== // Generate DMA IRQs. - let mut dma_irqs: BTreeMap<&str, Vec> = 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)| { @@ -1589,6 +1873,100 @@ fn main() { pub(crate) const DMA_CHANNELS: &[crate::dma::ChannelInfo] = &[#dmas]; }); + // ======== + // Generate gpio_block() function + + let gpio_base = METADATA.peripherals.iter().find(|p| p.name == "GPIOA").unwrap().address as usize; + let gpio_stride = 0x400 as usize; + + for p in METADATA.peripherals { + if let Some(bi) = &p.registers { + if bi.kind == "gpio" { + assert_eq!(0, (p.address as usize - gpio_base) % gpio_stride); + } + } + } + + g.extend(quote!( + pub fn gpio_block(n: usize) -> crate::pac::gpio::Gpio {{ + unsafe {{ crate::pac::gpio::Gpio::from_ptr((#gpio_base + #gpio_stride*n) as _) }} + }} + )); + + // ======== + // Generate flash constants + + let flash_regions: Vec<&MemoryRegion> = memory + .iter() + .filter(|x| x.kind == MemoryRegionKind::Flash && x.name.starts_with("BANK_")) + .collect(); + let first_flash = flash_regions.first().unwrap(); + let total_flash_size = flash_regions + .iter() + .map(|x| x.size) + .reduce(|acc, item| acc + item) + .unwrap(); + let write_sizes: HashSet<_> = flash_regions + .iter() + .map(|r| r.settings.as_ref().unwrap().write_size) + .collect(); + assert_eq!(1, write_sizes.len()); + + let flash_base = first_flash.address as usize; + let total_flash_size = total_flash_size as usize; + let write_size = (*write_sizes.iter().next().unwrap()) as usize; + + g.extend(quote!( + pub const FLASH_BASE: usize = #flash_base; + pub const FLASH_SIZE: usize = #total_flash_size; + pub const WRITE_SIZE: usize = #write_size; + )); + + // ======== + // Generate EEPROM constants + + cfgs.declare("eeprom"); + + let eeprom_memory_regions: Vec<&MemoryRegion> = + memory.iter().filter(|x| x.kind == MemoryRegionKind::Eeprom).collect(); + + if !eeprom_memory_regions.is_empty() { + cfgs.enable("eeprom"); + + let mut sorted_eeprom_regions = eeprom_memory_regions.clone(); + sorted_eeprom_regions.sort_by_key(|r| r.address); + + let first_eeprom_address = sorted_eeprom_regions[0].address; + let mut total_eeprom_size = 0; + let mut current_expected_address = first_eeprom_address; + + for region in sorted_eeprom_regions.iter() { + if region.address != current_expected_address { + // For STM32L0 and STM32L1, EEPROM regions (if multiple) are expected to be contiguous. + // If they are not, this indicates an issue with the chip metadata or an unsupported configuration. + panic!( + "EEPROM regions for chip {} are not contiguous, which is unexpected for L0/L1 series. \ + First region: '{}' at {:#X}. Found next non-contiguous region: '{}' at {:#X}. \ + Please verify chip metadata. Embassy currently assumes contiguous EEPROM for these series.", + chip_name, sorted_eeprom_regions[0].name, first_eeprom_address, region.name, region.address + ); + } + total_eeprom_size += region.size; + current_expected_address += region.size; + } + + let eeprom_base_usize = first_eeprom_address as usize; + let total_eeprom_size_usize = total_eeprom_size as usize; + + g.extend(quote! { + pub const EEPROM_BASE: usize = #eeprom_base_usize; + pub const EEPROM_SIZE: usize = #total_eeprom_size_usize; + }); + } + + // ======== + // Generate macro-tables + for irq in METADATA.interrupts { let name = irq.name.to_ascii_uppercase(); interrupts_table.push(vec![name.clone()]); @@ -1683,6 +2061,11 @@ fn main() { } println!("cargo:rerun-if-changed=build.rs"); + + if cfg!(feature = "memory-x") { + gen_memory_x(memory, out_dir); + println!("cargo:rustc-link-search={}", out_dir.display()); + } } enum GetOneError { @@ -1768,3 +2151,97 @@ fn rustfmt(path: impl AsRef) { } } } + +fn gen_memory_x(memory: &[MemoryRegion], out_dir: &Path) { + let mut memory_x = String::new(); + + let flash = get_memory_range(memory, MemoryRegionKind::Flash); + let ram = get_memory_range(memory, MemoryRegionKind::Ram); + + write!(memory_x, "MEMORY\n{{\n").unwrap(); + writeln!( + memory_x, + " FLASH : ORIGIN = 0x{:08x}, LENGTH = {:>4}K /* {} */", + flash.0, + flash.1 / 1024, + flash.2 + ) + .unwrap(); + writeln!( + memory_x, + " RAM : ORIGIN = 0x{:08x}, LENGTH = {:>4}K /* {} */", + ram.0, + ram.1 / 1024, + ram.2 + ) + .unwrap(); + write!(memory_x, "}}").unwrap(); + + std::fs::write(out_dir.join("memory.x"), memory_x.as_bytes()).unwrap(); +} + +fn get_memory_range(memory: &[MemoryRegion], kind: MemoryRegionKind) -> (u32, u32, String) { + let mut mems: Vec<_> = memory.iter().filter(|m| m.kind == kind && m.size != 0).collect(); + mems.sort_by_key(|m| m.address); + + let mut start = u32::MAX; + let mut end = u32::MAX; + let mut names = Vec::new(); + let mut best: Option<(u32, u32, String)> = None; + for m in mems { + if !mem_filter(&METADATA.name, &m.name) { + continue; + } + + if m.address != end { + names = Vec::new(); + start = m.address; + end = m.address; + } + + end += m.size; + names.push(m.name.to_string()); + + if best.is_none() || end - start > best.as_ref().unwrap().1 { + best = Some((start, end - start, names.join(" + "))); + } + } + + best.unwrap() +} + +fn mem_filter(chip: &str, region: &str) -> bool { + // in STM32WB, SRAM2a/SRAM2b are reserved for the radio core. + if chip.starts_with("STM32WB") + && !chip.starts_with("STM32WBA") + && !chip.starts_with("STM32WB0") + && region.starts_with("SRAM2") + { + return false; + } + + true +} + +#[derive(Copy, Clone, Debug)] +struct Frac { + num: u32, + denom: u32, +} + +impl Frac { + fn simplify(self) -> Self { + let d = gcd(self.num, self.denom); + Self { + num: self.num / d, + denom: self.denom / d, + } + } +} + +fn gcd(a: u32, b: u32) -> u32 { + if b == 0 { + return a; + } + gcd(b, a % b) +} diff --git a/embassy-stm32/src/adc/c0.rs b/embassy-stm32/src/adc/c0.rs new file mode 100644 index 000000000..936ad7413 --- /dev/null +++ b/embassy-stm32/src/adc/c0.rs @@ -0,0 +1,467 @@ +use pac::adc::vals::Scandir; +#[allow(unused)] +use pac::adc::vals::{Adstp, Align, Ckmode, Dmacfg, Exten, Ovrmod, Ovsr}; +use pac::adccommon::vals::Presc; + +use super::{ + blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, +}; +use crate::dma::Transfer; +use crate::time::Hertz; +use crate::{pac, rcc, Peri}; + +/// Default VREF voltage used for sample conversion to millivolts. +pub const VREF_DEFAULT_MV: u32 = 3300; +/// VREF voltage used for factory calibration of VREFINTCAL register. +pub const VREF_CALIB_MV: u32 = 3300; + +const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(25); + +const TIME_ADC_VOLTAGE_REGUALTOR_STARTUP_US: u32 = 20; + +const TEMP_CHANNEL: u8 = 9; +const VREF_CHANNEL: u8 = 10; + +const NUM_HW_CHANNELS: u8 = 22; +const CHSELR_SQ_SIZE: usize = 8; +const CHSELR_SQ_MAX_CHANNEL: u8 = 14; +const CHSELR_SQ_SEQUENCE_END_MARKER: u8 = 0b1111; + +// NOTE: Vrefint/Temperature/Vbat are not available on all ADCs, +// this currently cannot be modeled with stm32-data, +// so these are available from the software on all ADCs. +/// Internal voltage reference channel. +pub struct VrefInt; +impl AdcChannel for VrefInt {} +impl SealedAdcChannel for VrefInt { + fn channel(&self) -> u8 { + VREF_CHANNEL + } +} + +/// Internal temperature channel. +pub struct Temperature; +impl AdcChannel for Temperature {} +impl SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + TEMP_CHANNEL + } +} + +#[derive(Debug)] +pub enum Prescaler { + NotDivided, + DividedBy2, + DividedBy4, + DividedBy6, + DividedBy8, + DividedBy10, + DividedBy12, + DividedBy16, + DividedBy32, + DividedBy64, + DividedBy128, + DividedBy256, +} + +impl Prescaler { + fn from_ker_ck(frequency: Hertz) -> Self { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Self::NotDivided, + 1 => Self::DividedBy2, + 2..=3 => Self::DividedBy4, + 4..=5 => Self::DividedBy6, + 6..=7 => Self::DividedBy8, + 8..=9 => Self::DividedBy10, + 10..=11 => Self::DividedBy12, + _ => unimplemented!(), + } + } + + #[allow(unused)] + fn divisor(&self) -> u32 { + match self { + Prescaler::NotDivided => 1, + Prescaler::DividedBy2 => 2, + Prescaler::DividedBy4 => 4, + Prescaler::DividedBy6 => 6, + Prescaler::DividedBy8 => 8, + Prescaler::DividedBy10 => 10, + Prescaler::DividedBy12 => 12, + Prescaler::DividedBy16 => 16, + Prescaler::DividedBy32 => 32, + Prescaler::DividedBy64 => 64, + Prescaler::DividedBy128 => 128, + Prescaler::DividedBy256 => 256, + } + } + + fn presc(&self) -> Presc { + match self { + Prescaler::NotDivided => Presc::DIV1, + Prescaler::DividedBy2 => Presc::DIV2, + Prescaler::DividedBy4 => Presc::DIV4, + Prescaler::DividedBy6 => Presc::DIV6, + Prescaler::DividedBy8 => Presc::DIV8, + Prescaler::DividedBy10 => Presc::DIV10, + Prescaler::DividedBy12 => Presc::DIV12, + Prescaler::DividedBy16 => Presc::DIV16, + Prescaler::DividedBy32 => Presc::DIV32, + Prescaler::DividedBy64 => Presc::DIV64, + Prescaler::DividedBy128 => Presc::DIV128, + Prescaler::DividedBy256 => Presc::DIV256, + } + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Prescaler { + fn format(&self, fmt: defmt::Formatter) { + match self { + Prescaler::NotDivided => defmt::write!(fmt, "Prescaler::NotDivided"), + Prescaler::DividedBy2 => defmt::write!(fmt, "Prescaler::DividedBy2"), + Prescaler::DividedBy4 => defmt::write!(fmt, "Prescaler::DividedBy4"), + Prescaler::DividedBy6 => defmt::write!(fmt, "Prescaler::DividedBy6"), + Prescaler::DividedBy8 => defmt::write!(fmt, "Prescaler::DividedBy8"), + Prescaler::DividedBy10 => defmt::write!(fmt, "Prescaler::DividedBy10"), + Prescaler::DividedBy12 => defmt::write!(fmt, "Prescaler::DividedBy12"), + Prescaler::DividedBy16 => defmt::write!(fmt, "Prescaler::DividedBy16"), + Prescaler::DividedBy32 => defmt::write!(fmt, "Prescaler::DividedBy32"), + Prescaler::DividedBy64 => defmt::write!(fmt, "Prescaler::DividedBy64"), + Prescaler::DividedBy128 => defmt::write!(fmt, "Prescaler::DividedBy128"), + Prescaler::DividedBy256 => defmt::write!(fmt, "Prescaler::DividedBy256"), + } + } +} + +/// Number of samples used for averaging. +/// TODO: Implement hardware averaging setting. +#[allow(unused)] +pub enum Averaging { + Disabled, + Samples2, + Samples4, + Samples8, + Samples16, + Samples32, + Samples64, + Samples128, + Samples256, + Samples512, + Samples1024, +} + +impl<'d, T: Instance> Adc<'d, T> { + /// Create a new ADC driver. + pub fn new(adc: Peri<'d, T>, sample_time: SampleTime, resolution: Resolution) -> Self { + rcc::enable_and_reset::(); + + T::regs().cfgr2().modify(|w| w.set_ckmode(Ckmode::SYSCLK)); + + let prescaler = Prescaler::from_ker_ck(T::frequency()); + T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + + let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + debug!("ADC frequency set to {}", frequency); + + if frequency > MAX_ADC_CLK_FREQ { + panic!("Maximal allowed frequency for the ADC is {} MHz and it varies with different packages, refer to ST docs for more information.", MAX_ADC_CLK_FREQ.0 / 1_000_000 ); + } + + let mut s = Self { + adc, + sample_time: SampleTime::from_bits(0), + }; + + s.power_up(); + + s.set_resolution(resolution); + + s.calibrate(); + + s.enable(); + + s.configure_default(); + + s.set_sample_time_all_channels(sample_time); + + s + } + + fn power_up(&mut self) { + T::regs().cr().modify(|reg| { + reg.set_advregen(true); + }); + + // "The software must wait for the ADC voltage regulator startup time." + // See datasheet for the value. + blocking_delay_us(TIME_ADC_VOLTAGE_REGUALTOR_STARTUP_US + 1); + } + + fn calibrate(&mut self) { + // We have to make sure AUTOFF is OFF, but keep its value after calibration. + let autoff_value = T::regs().cfgr1().read().autoff(); + T::regs().cfgr1().modify(|w| w.set_autoff(false)); + + T::regs().cr().modify(|w| w.set_adcal(true)); + + // "ADCAL bit stays at 1 during all the calibration sequence." + // "It is then cleared by hardware as soon the calibration completes." + while T::regs().cr().read().adcal() {} + + debug!("ADC calibration value: {}.", T::regs().dr().read().data()); + + T::regs().cfgr1().modify(|w| w.set_autoff(autoff_value)); + } + + fn enable(&mut self) { + T::regs().isr().modify(|w| w.set_adrdy(true)); + T::regs().cr().modify(|w| w.set_aden(true)); + // ADRDY is "ADC ready". Wait until it will be True. + while !T::regs().isr().read().adrdy() {} + } + + fn configure_default(&mut self) { + // single conversion mode, software trigger + T::regs().cfgr1().modify(|w| { + w.set_cont(false); + w.set_exten(Exten::DISABLED); + w.set_align(Align::RIGHT); + }); + } + + /// Enable reading the voltage reference internal channel. + pub fn enable_vrefint(&self) -> VrefInt { + T::common_regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); + + VrefInt {} + } + + /// Enable reading the temperature internal channel. + pub fn enable_temperature(&self) -> Temperature { + debug!("Ensure that sample time is set to more than temperature sensor T_start from the datasheet!"); + T::common_regs().ccr().modify(|reg| { + reg.set_tsen(true); + }); + + Temperature {} + } + + /// Set the ADC sample time. + /// Shall only be called when ADC is not converting. + pub fn set_sample_time_all_channels(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + + // Set all channels to use SMP1 field as source. + T::regs().smpr().modify(|w| { + w.smpsel(0); + w.set_smp1(sample_time); + }); + } + + /// Set the ADC resolution. + pub fn set_resolution(&mut self, resolution: Resolution) { + T::regs().cfgr1().modify(|reg| reg.set_res(resolution)); + } + + /// Perform a single conversion. + fn convert(&mut self) -> u16 { + // Set single conversion mode. + T::regs().cfgr1().modify(|w| w.set_cont(false)); + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Waiting for End Of Conversion (EOC). + while !T::regs().isr().read().eoc() {} + + T::regs().dr().read().data() as u16 + } + + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + Self::configure_channel(channel); + T::regs().cfgr1().write(|reg| { + reg.set_chselrmod(false); + reg.set_align(Align::RIGHT); + }); + self.convert() + } + + fn setup_channel_sequencer<'a>(channel_sequence: impl ExactSizeIterator>) { + assert!( + channel_sequence.len() <= CHSELR_SQ_SIZE, + "Seqenced read set cannot be more than {} in size.", + CHSELR_SQ_SIZE + ); + let mut last_sq_set: usize = 0; + T::regs().chselr_sq().write(|w| { + for (i, channel) in channel_sequence.enumerate() { + assert!( + channel.channel() <= CHSELR_SQ_MAX_CHANNEL, + "Sequencer only support HW channels smaller than {}.", + CHSELR_SQ_MAX_CHANNEL + ); + w.set_sq(i, channel.channel()); + last_sq_set = i; + } + + for i in (last_sq_set + 1)..CHSELR_SQ_SIZE { + w.set_sq(i, CHSELR_SQ_SEQUENCE_END_MARKER); + } + }); + + Self::apply_channel_conf() + } + + async fn dma_convert(&mut self, rx_dma: Peri<'_, impl RxDma>, readings: &mut [u16]) { + // Enable overrun control, so no new DMA requests will be generated until + // previous DR values is read. + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + + // Set continuous mode with oneshot dma. + T::regs().cfgr1().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::DMA_ONE_SHOT); + reg.set_dmaen(true); + reg.set_ovrmod(Ovrmod::PRESERVE); + }); + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + readings, + Default::default(), + ) + }; + + // Start conversion. + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Wait for conversion sequence to finish. + transfer.await; + + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + T::regs().cfgr1().modify(|reg| { + reg.set_cont(false); + reg.set_dmacfg(Dmacfg::from_bits(0)); + reg.set_dmaen(false); + }); + } + + /// Read one or multiple ADC channels using DMA in hardware order. + /// Readings will be ordered based on **hardware** ADC channel number and `scandir` setting. + /// Readings won't be in the same order as in the `set`! + /// + /// In STM32C0, channels bigger than 14 cannot be read using sequencer, so you have to use + /// either blocking reads or use the mechanism to read in HW order (CHSELRMOD=0). + /// TODO(chudsaviet): externalize generic code and merge with read(). + pub async fn read_in_hw_order( + &mut self, + rx_dma: Peri<'_, impl RxDma>, + hw_channel_selection: u32, + scandir: Scandir, + readings: &mut [u16], + ) { + assert!( + hw_channel_selection != 0, + "Some bits in `hw_channel_selection` shall be set." + ); + assert!( + (hw_channel_selection >> NUM_HW_CHANNELS) == 0, + "STM32C0 only have {} ADC channels. `hw_channel_selection` cannot have bits higher than this number set.", + NUM_HW_CHANNELS + ); + // To check for correct readings slice size, we shall solve Hamming weight problem, + // which is either slow or memory consuming. + // Since we have limited resources, we don't do it here. + // Not doing this have a great potential for a bug through. + + // Ensure no conversions are ongoing. + Self::cancel_conversions(); + + T::regs().cfgr1().modify(|reg| { + reg.set_chselrmod(false); + reg.set_scandir(scandir); + reg.set_align(Align::RIGHT); + }); + + // Set required channels for multi-convert. + unsafe { (T::regs().chselr().as_ptr() as *mut u32).write_volatile(hw_channel_selection) } + + Self::apply_channel_conf(); + + self.dma_convert(rx_dma, readings).await + } + + // Read ADC channels in specified order using DMA (CHSELRMOD = 1). + // In STM32C0, only lower 14 ADC channels can be read this way. + // For other channels, use `read_in_hw_order()` or blocking read. + pub async fn read( + &mut self, + rx_dma: Peri<'_, impl RxDma>, + channel_sequence: impl ExactSizeIterator>, + readings: &mut [u16], + ) { + assert!( + channel_sequence.len() != 0, + "Asynchronous read channel sequence cannot be empty." + ); + assert!( + channel_sequence.len() == readings.len(), + "Channel sequence length must be equal to readings length." + ); + + // Ensure no conversions are ongoing. + Self::cancel_conversions(); + + T::regs().cfgr1().modify(|reg| { + reg.set_chselrmod(true); + reg.set_align(Align::RIGHT); + }); + + Self::setup_channel_sequencer(channel_sequence); + + self.dma_convert(rx_dma, readings).await + } + + fn configure_channel(channel: &mut impl AdcChannel) { + channel.setup(); + // write() because we want all other bits to be set to 0. + T::regs() + .chselr() + .write(|w| w.set_chsel(channel.channel().into(), true)); + + Self::apply_channel_conf(); + } + + fn apply_channel_conf() { + // Trigger and wait for the channel selection procedure to complete. + T::regs().isr().modify(|w| w.set_ccrdy(false)); + while !T::regs().isr().read().ccrdy() {} + } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(Adstp::STOP); + }); + while T::regs().cr().read().adstart() {} + } + } +} diff --git a/embassy-stm32/src/adc/f1.rs b/embassy-stm32/src/adc/f1.rs index b37ec260f..3cdc9d8fb 100644 --- a/embassy-stm32/src/adc/f1.rs +++ b/embassy-stm32/src/adc/f1.rs @@ -2,12 +2,12 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::into_ref; - use super::blocking_delay_us; use crate::adc::{Adc, AdcChannel, Instance, SampleTime}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::{self}; use crate::time::Hertz; -use crate::{interrupt, rcc, Peripheral}; +use crate::{rcc, Peri}; pub const VDDA_CALIB_MV: u32 = 3300; pub const ADC_MAX: u32 = (1 << 12) - 1; @@ -22,12 +22,9 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { if T::regs().sr().read().eoc() { - T::regs().cr1().modify(|w| w.set_eocie(false)); - } else { - return; + T::regs().cr1().modify(|w| w.set_eocie(false)); // End of Convert interrupt disable + T::state().waker.wake(); } - - T::state().waker.wake(); } } @@ -48,8 +45,7 @@ impl super::SealedAdcChannel for Temperature { } impl<'d, T: Instance> Adc<'d, T> { - pub fn new(adc: impl Peripheral

+ 'd) -> Self { - into_ref!(adc); + pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); T::regs().cr2().modify(|reg| reg.set_adon(true)); @@ -72,6 +68,9 @@ impl<'d, T: Instance> Adc<'d, T> { // One cycle after calibration blocking_delay_us((1_000_000 * 1) / Self::freq().0 + 1); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + Self { adc, sample_time: SampleTime::from_bits(0), diff --git a/embassy-stm32/src/adc/f3.rs b/embassy-stm32/src/adc/f3.rs index ac88c9742..3aeb6f2c7 100644 --- a/embassy-stm32/src/adc/f3.rs +++ b/embassy-stm32/src/adc/f3.rs @@ -2,13 +2,11 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::into_ref; - use super::blocking_delay_us; use crate::adc::{Adc, AdcChannel, Instance, SampleTime}; use crate::interrupt::typelevel::Interrupt; use crate::time::Hertz; -use crate::{interrupt, rcc, Peripheral}; +use crate::{interrupt, rcc, Peri}; pub const VDDA_CALIB_MV: u32 = 3300; pub const ADC_MAX: u32 = (1 << 12) - 1; @@ -42,7 +40,7 @@ impl super::SealedAdcChannel for Vref { impl Vref { /// The value that vref would be if vdda was at 3300mv pub fn value(&self) -> u16 { - crate::pac::VREFINTCAL.data().read().value() + crate::pac::VREFINTCAL.data().read() } } @@ -56,13 +54,11 @@ impl super::SealedAdcChannel for Temperature { impl<'d, T: Instance> Adc<'d, T> { pub fn new( - adc: impl Peripheral

+ 'd, + adc: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { use crate::pac::adc::vals; - into_ref!(adc); - rcc::enable_and_reset::(); // Enable the adc regulator diff --git a/embassy-stm32/src/adc/f3_v1_1.rs b/embassy-stm32/src/adc/f3_v1_1.rs index 689c2871d..944e971bb 100644 --- a/embassy-stm32/src/adc/f3_v1_1.rs +++ b/embassy-stm32/src/adc/f3_v1_1.rs @@ -3,14 +3,13 @@ use core::marker::PhantomData; use core::task::Poll; use embassy_futures::yield_now; -use embassy_hal_internal::into_ref; use embassy_time::Instant; use super::Resolution; use crate::adc::{Adc, AdcChannel, Instance, SampleTime}; use crate::interrupt::typelevel::Interrupt; use crate::time::Hertz; -use crate::{interrupt, rcc, Peripheral}; +use crate::{interrupt, rcc, Peri}; const ADC_FREQ: Hertz = crate::rcc::HSI_FREQ; @@ -74,7 +73,7 @@ impl super::SealedAdcChannel for Vref { impl Vref { /// The value that vref would be if vdda was at 3000mv pub fn calibrated_value(&self) -> u16 { - crate::pac::VREFINTCAL.data().read().value() + crate::pac::VREFINTCAL.data().read() } pub async fn calibrate(&mut self, adc: &mut Adc<'_, T>) -> Calibration { @@ -138,11 +137,9 @@ impl Drop for Temperature { impl<'d, T: Instance> Adc<'d, T> { pub fn new( - adc: impl Peripheral

+ 'd, + adc: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { - into_ref!(adc); - rcc::enable_and_reset::(); //let r = T::regs(); diff --git a/embassy-stm32/src/adc/g4.rs b/embassy-stm32/src/adc/g4.rs index c1e584f59..1fce3085a 100644 --- a/embassy-stm32/src/adc/g4.rs +++ b/embassy-stm32/src/adc/g4.rs @@ -1,10 +1,17 @@ #[allow(unused)] +#[cfg(stm32h7)] use pac::adc::vals::{Adcaldif, Difsel, Exten}; +#[allow(unused)] +#[cfg(stm32g4)] +use pac::adc::vals::{Adcaldif, Difsel, Exten, Rovsm, Trovs}; use pac::adccommon::vals::Presc; +use stm32_metapac::adc::vals::{Adstp, Dmacfg, Dmaen}; -use super::{blocking_delay_us, Adc, AdcChannel, Instance, Resolution, SampleTime}; +use super::{blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime}; +use crate::adc::SealedAdcChannel; +use crate::dma::Transfer; use crate::time::Hertz; -use crate::{pac, rcc, Peripheral}; +use crate::{pac, rcc, Peri}; /// Default VREF voltage used for sample conversion to millivolts. pub const VREF_DEFAULT_MV: u32 = 3300; @@ -128,8 +135,7 @@ impl Prescaler { impl<'d, T: Instance> Adc<'d, T> { /// Create a new ADC driver. - pub fn new(adc: impl Peripheral

+ 'd) -> Self { - embassy_hal_internal::into_ref!(adc); + pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); let prescaler = Prescaler::from_ker_ck(T::frequency()); @@ -137,7 +143,7 @@ impl<'d, T: Instance> Adc<'d, T> { T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); let frequency = Hertz(T::frequency().0 / prescaler.divisor()); - info!("ADC frequency set to {} Hz", frequency.0); + trace!("ADC frequency set to {}", frequency); if frequency > MAX_ADC_CLK_FREQ { panic!("Maximal allowed frequency for the ADC is {} MHz and it varies with different packages, refer to ST docs for more information.", MAX_ADC_CLK_FREQ.0 / 1_000_000 ); @@ -165,32 +171,58 @@ impl<'d, T: Instance> Adc<'d, T> { reg.set_advregen(true); }); - blocking_delay_us(10); + blocking_delay_us(20); } fn configure_differential_inputs(&mut self) { T::regs().difsel().modify(|w| { for n in 0..18 { - w.set_difsel(n, Difsel::SINGLEENDED); + w.set_difsel(n, Difsel::SINGLE_ENDED); } }); } fn calibrate(&mut self) { T::regs().cr().modify(|w| { - w.set_adcaldif(Adcaldif::SINGLEENDED); + w.set_adcaldif(Adcaldif::SINGLE_ENDED); }); T::regs().cr().modify(|w| w.set_adcal(true)); while T::regs().cr().read().adcal() {} + + blocking_delay_us(20); + + T::regs().cr().modify(|w| { + w.set_adcaldif(Adcaldif::DIFFERENTIAL); + }); + + T::regs().cr().modify(|w| w.set_adcal(true)); + + while T::regs().cr().read().adcal() {} + + blocking_delay_us(20); } fn enable(&mut self) { - T::regs().isr().write(|w| w.set_adrdy(true)); - T::regs().cr().modify(|w| w.set_aden(true)); - while !T::regs().isr().read().adrdy() {} - T::regs().isr().write(|w| w.set_adrdy(true)); + // Make sure bits are off + while T::regs().cr().read().addis() { + // spin + } + + if !T::regs().cr().read().aden() { + // Enable ADC + T::regs().isr().modify(|reg| { + reg.set_adrdy(true); + }); + T::regs().cr().modify(|reg| { + reg.set_aden(true); + }); + + while !T::regs().isr().read().adrdy() { + // spin + } + } } fn configure(&mut self) { @@ -228,6 +260,68 @@ impl<'d, T: Instance> Adc<'d, T> { Vbat {} } + /// Enable differential channel. + /// Caution: + /// : When configuring the channel “i” in differential input mode, its negative input voltage VINN[i] + /// is connected to another channel. As a consequence, this channel is no longer usable in + /// single-ended mode or in differential mode and must never be configured to be converted. + /// Some channels are shared between ADC1/ADC2/ADC3/ADC4/ADC5: this can make the + /// channel on the other ADC unusable. The only exception is when ADC master and the slave + /// operate in interleaved mode. + #[cfg(stm32g4)] + pub fn set_differential_channel(&mut self, ch: usize, enable: bool) { + T::regs().cr().modify(|w| w.set_aden(false)); // disable adc + T::regs().difsel().modify(|w| { + w.set_difsel( + ch, + if enable { + Difsel::DIFFERENTIAL + } else { + Difsel::SINGLE_ENDED + }, + ); + }); + T::regs().cr().modify(|w| w.set_aden(true)); + } + + #[cfg(stm32g4)] + pub fn set_differential(&mut self, channel: &mut impl AdcChannel, enable: bool) { + self.set_differential_channel(channel.channel() as usize, enable); + } + + /// Set oversampling shift. + #[cfg(stm32g4)] + pub fn set_oversampling_shift(&mut self, shift: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovss(shift)); + } + + /// Set oversampling ratio. + #[cfg(stm32g4)] + pub fn set_oversampling_ratio(&mut self, ratio: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio)); + } + + /// Enable oversampling in regular mode. + #[cfg(stm32g4)] + pub fn enable_regular_oversampling_mode(&mut self, mode: Rovsm, trig_mode: Trovs, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovsm(mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + } + + // Reads that are not implemented as INJECTED in "blocking_read" + // #[cfg(stm32g4)] + // pub fn enalble_injected_oversampling_mode(&mut self, enable: bool) { + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + + // #[cfg(stm32g4)] + // pub fn enable_oversampling_regular_injected_mode(&mut self, enable: bool) { + // // the regularoversampling mode is forced to resumed mode (ROVSM bit ignored), + // T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + /// Set the ADC sample time. pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; @@ -261,23 +355,146 @@ impl<'d, T: Instance> Adc<'d, T> { pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { channel.setup(); - self.read_channel(channel.channel()) + self.read_channel(channel) } - fn read_channel(&mut self, channel: u8) -> u16 { - // Configure channel - Self::set_channel_sample_time(channel, self.sample_time); + /// Read one or multiple ADC channels using DMA. + /// + /// `sequence` iterator and `readings` must have the same length. + /// + /// Example + /// ```rust,ignore + /// use embassy_stm32::adc::{Adc, AdcChannel} + /// + /// let mut adc = Adc::new(p.ADC1); + /// let mut adc_pin0 = p.PA0.into(); + /// let mut adc_pin1 = p.PA1.into(); + /// let mut measurements = [0u16; 2]; + /// + /// adc.read( + /// p.DMA1_CH2.reborrow(), + /// [ + /// (&mut *adc_pin0, SampleTime::CYCLES160_5), + /// (&mut *adc_pin1, SampleTime::CYCLES160_5), + /// ] + /// .into_iter(), + /// &mut measurements, + /// ) + /// .await; + /// defmt::info!("measurements: {}", measurements); + /// ``` + pub async fn read( + &mut self, + rx_dma: Peri<'_, impl RxDma>, + sequence: impl ExactSizeIterator, SampleTime)>, + readings: &mut [u16], + ) { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); + assert!( + sequence.len() <= 16, + "Asynchronous read sequence cannot be more than 16 in length" + ); + // Ensure no conversions are ongoing and ADC is enabled. + Self::cancel_conversions(); + self.enable(); + + // Set sequence length + T::regs().sqr1().modify(|w| { + w.set_l(sequence.len() as u8 - 1); + }); + + // Configure channels and ranks + for (_i, (channel, sample_time)) in sequence.enumerate() { + Self::configure_channel(channel, sample_time); + + match _i { + 0..=3 => { + T::regs().sqr1().modify(|w| { + w.set_sq(_i, channel.channel()); + }); + } + 4..=8 => { + T::regs().sqr2().modify(|w| { + w.set_sq(_i - 4, channel.channel()); + }); + } + 9..=13 => { + T::regs().sqr3().modify(|w| { + w.set_sq(_i - 9, channel.channel()); + }); + } + 14..=15 => { + T::regs().sqr4().modify(|w| { + w.set_sq(_i - 14, channel.channel()); + }); + } + _ => unreachable!(), + } + } + + // Set continuous mode with oneshot dma. + // Clear overrun flag before starting transfer. + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + }); + + T::regs().cfgr().modify(|reg| { + reg.set_discen(false); + reg.set_cont(true); + reg.set_dmacfg(Dmacfg::ONE_SHOT); + reg.set_dmaen(Dmaen::ENABLE); + }); + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + readings, + Default::default(), + ) + }; + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + // Wait for conversion sequence to finish. + transfer.await; + + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + T::regs().cfgr().modify(|reg| { + reg.set_cont(false); + }); + } + + fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { + // Configure channel + Self::set_channel_sample_time(channel.channel(), sample_time); + } + + fn read_channel(&mut self, channel: &mut impl AdcChannel) -> u16 { + Self::configure_channel(channel, self.sample_time); #[cfg(stm32h7)] { T::regs().cfgr2().modify(|w| w.set_lshift(0)); T::regs() .pcsel() - .write(|w| w.set_pcsel(channel as _, Pcsel::PRESELECTED)); + .write(|w| w.set_pcsel(channel.channel() as _, Pcsel::PRESELECTED)); } T::regs().sqr1().write(|reg| { - reg.set_sq(0, channel); + reg.set_sq(0, channel.channel()); reg.set_l(0); }); @@ -292,4 +509,13 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().smpr2().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(Adstp::STOP); + }); + while T::regs().cr().read().adstart() {} + } + } } diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 2f36df240..f46e87f38 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -4,37 +4,46 @@ #![allow(missing_docs)] // TODO #![cfg_attr(adc_f3_v2, allow(unused))] -#[cfg(not(adc_f3_v2))] +#[cfg(not(any(adc_f3_v2)))] #[cfg_attr(adc_f1, path = "f1.rs")] #[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, adc_h5, adc_u0), path = "v3.rs")] -#[cfg_attr(adc_v4, path = "v4.rs")] +#[cfg_attr(any(adc_v3, adc_g0, adc_h5, adc_h7rs, adc_u0), path = "v3.rs")] +#[cfg_attr(any(adc_v4, adc_u5), path = "v4.rs")] #[cfg_attr(adc_g4, path = "g4.rs")] +#[cfg_attr(adc_c0, path = "c0.rs")] mod _version; use core::marker::PhantomData; #[allow(unused)] -#[cfg(not(adc_f3_v2))] +#[cfg(not(any(adc_f3_v2)))] pub use _version::*; +use embassy_hal_internal::{impl_peripheral, PeripheralType}; #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] use embassy_sync::waitqueue::AtomicWaker; +#[cfg(adc_u5)] +#[path = "u5_adc4.rs"] +pub mod adc4; + +pub use crate::pac::adc::vals; #[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; dma_trait!(RxDma, Instance); +#[cfg(adc_u5)] +dma_trait!(RxDma4, adc4::Instance); /// Analog to Digital driver. pub struct Adc<'d, T: Instance> { #[allow(unused)] - adc: crate::PeripheralRef<'d, T>, + adc: crate::Peri<'d, T>, #[cfg(not(any(adc_f3_v2, adc_f3_v1_1)))] sample_time: SampleTime, } @@ -64,7 +73,7 @@ trait SealedInstance { } pub(crate) trait SealedAdcChannel { - #[cfg(any(adc_v1, adc_l0, adc_v2, adc_g4, adc_v4))] + #[cfg(any(adc_v1, adc_c0, adc_l0, adc_v2, adc_g4, adc_v4, adc_u5))] fn setup(&mut self) {} #[allow(unused)] @@ -98,10 +107,13 @@ pub(crate) fn blocking_delay_us(us: u32) { adc_f3_v1_1, adc_g0, adc_u0, - adc_h5 + adc_h5, + adc_h7rs, + adc_u5, + adc_c0 )))] #[allow(private_bounds)] -pub trait Instance: SealedInstance + crate::Peripheral

{ +pub trait Instance: SealedInstance + crate::PeripheralType { type Interrupt: crate::interrupt::typelevel::Interrupt; } /// ADC instance. @@ -117,10 +129,13 @@ pub trait Instance: SealedInstance + crate::Peripheral

{ adc_f3_v1_1, adc_g0, adc_u0, - adc_h5 + adc_h5, + adc_h7rs, + adc_u5, + adc_c0 ))] #[allow(private_bounds)] -pub trait Instance: SealedInstance + crate::Peripheral

+ crate::rcc::RccPeripheral { +pub trait Instance: SealedInstance + crate::PeripheralType + crate::rcc::RccPeripheral { type Interrupt: crate::interrupt::typelevel::Interrupt; } @@ -129,7 +144,7 @@ pub trait Instance: SealedInstance + crate::Peripheral

+ crate::rcc::R pub trait AdcChannel: SealedAdcChannel + Sized { #[allow(unused_mut)] fn degrade_adc(mut self) -> AnyAdcChannel { - #[cfg(any(adc_v1, adc_l0, adc_v2, adc_g4, adc_v4))] + #[cfg(any(adc_v1, adc_l0, adc_v2, adc_g4, adc_v4, adc_u5))] self.setup(); AnyAdcChannel { @@ -147,7 +162,7 @@ pub struct AnyAdcChannel { channel: u8, _phantom: PhantomData, } - +impl_peripheral!(AnyAdcChannel); impl AdcChannel for AnyAdcChannel {} impl SealedAdcChannel for AnyAdcChannel { fn channel(&self) -> u8 { @@ -155,6 +170,45 @@ impl SealedAdcChannel for AnyAdcChannel { } } +impl AnyAdcChannel { + #[allow(unused)] + pub fn get_hw_channel(&self) -> u8 { + self.channel + } +} + +#[cfg(adc_u5)] +foreach_adc!( + (ADC4, $common_inst:ident, $clock:ident) => { + impl crate::adc::adc4::SealedInstance for peripherals::ADC4 { + fn regs() -> crate::pac::adc::Adc4 { + crate::pac::ADC4 + } + } + + impl crate::adc::adc4::Instance for peripherals::ADC4 { + type Interrupt = crate::_generated::peripheral_interrupts::ADC4::GLOBAL; + } + }; + + ($inst:ident, $common_inst:ident, $clock:ident) => { + impl crate::adc::SealedInstance for peripherals::$inst { + fn regs() -> crate::pac::adc::Adc { + crate::pac::$inst + } + + fn common_regs() -> crate::pac::adccommon::AdcCommon { + return crate::pac::$common_inst + } + } + + impl crate::adc::Instance for peripherals::$inst { + type Interrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; + } + }; +); + +#[cfg(not(adc_u5))] foreach_adc!( ($inst:ident, $common_inst:ident, $clock:ident) => { impl crate::adc::SealedInstance for peripherals::$inst { @@ -182,11 +236,11 @@ foreach_adc!( macro_rules! impl_adc_pin { ($inst:ident, $pin:ident, $ch:expr) => { - impl crate::adc::AdcChannel for crate::peripherals::$pin {} - impl crate::adc::SealedAdcChannel for crate::peripherals::$pin { - #[cfg(any(adc_v1, adc_l0, adc_v2, adc_g4, adc_v4))] + impl crate::adc::AdcChannel for crate::Peri<'_, crate::peripherals::$pin> {} + impl crate::adc::SealedAdcChannel for crate::Peri<'_, crate::peripherals::$pin> { + #[cfg(any(adc_v1, adc_c0, adc_l0, adc_v2, adc_g4, adc_v4, adc_u5))] fn setup(&mut self) { - ::set_as_analog(self); + ::set_as_analog(self); } fn channel(&self) -> u8 { @@ -204,7 +258,7 @@ pub const fn resolution_to_max_count(res: Resolution) -> u32 { match res { #[cfg(adc_v4)] Resolution::BITS16 => (1 << 16) - 1, - #[cfg(adc_v4)] + #[cfg(any(adc_v4, adc_u5))] Resolution::BITS14 => (1 << 14) - 1, #[cfg(adc_v4)] Resolution::BITS14V => (1 << 14) - 1, @@ -213,7 +267,7 @@ pub const fn resolution_to_max_count(res: Resolution) -> u32 { 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))] + #[cfg(any(adc_v1, adc_v2, adc_v3, adc_l0, adc_c0, 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/ringbuffered_v2.rs b/embassy-stm32/src/adc/ringbuffered_v2.rs index 3b064044e..6f69e8486 100644 --- a/embassy-stm32/src/adc/ringbuffered_v2.rs +++ b/embassy-stm32/src/adc/ringbuffered_v2.rs @@ -2,14 +2,15 @@ use core::marker::PhantomData; use core::mem; use core::sync::atomic::{compiler_fence, Ordering}; -use embassy_hal_internal::{into_ref, Peripheral}; use stm32_metapac::adc::vals::SampleTime; use crate::adc::{Adc, AdcChannel, Instance, RxDma}; -use crate::dma::ringbuffer::OverrunError; use crate::dma::{Priority, ReadableRingBuffer, TransferOptions}; use crate::pac::adc::vals; -use crate::rcc; +use crate::{rcc, Peri}; + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OverrunError; fn clear_interrupt_flags(r: crate::pac::adc::Adc) { r.sr().modify(|regs| { @@ -101,13 +102,8 @@ impl<'d, T: Instance> Adc<'d, T> { /// It is critical to call `read` frequently to prevent DMA buffer overrun. /// /// [`read`]: #method.read - pub fn into_ring_buffered( - self, - dma: impl Peripheral

> + 'd, - dma_buf: &'d mut [u16], - ) -> RingBufferedAdc<'d, T> { + pub fn into_ring_buffered(self, dma: Peri<'d, impl RxDma>, dma_buf: &'d mut [u16]) -> RingBufferedAdc<'d, T> { assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); - into_ref!(dma); let opts: crate::dma::TransferOptions = TransferOptions { half_transfer_ir: true, @@ -203,16 +199,16 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { Sequence::Four => T::regs().sqr3().modify(|w| w.set_sq(3, channel.channel())), Sequence::Five => T::regs().sqr3().modify(|w| w.set_sq(4, channel.channel())), Sequence::Six => T::regs().sqr3().modify(|w| w.set_sq(5, channel.channel())), - Sequence::Seven => T::regs().sqr2().modify(|w| w.set_sq(6, channel.channel())), - Sequence::Eight => T::regs().sqr2().modify(|w| w.set_sq(7, channel.channel())), - Sequence::Nine => T::regs().sqr2().modify(|w| w.set_sq(8, channel.channel())), - Sequence::Ten => T::regs().sqr2().modify(|w| w.set_sq(9, channel.channel())), - Sequence::Eleven => T::regs().sqr2().modify(|w| w.set_sq(10, channel.channel())), - Sequence::Twelve => T::regs().sqr2().modify(|w| w.set_sq(11, channel.channel())), - Sequence::Thirteen => T::regs().sqr1().modify(|w| w.set_sq(12, channel.channel())), - Sequence::Fourteen => T::regs().sqr1().modify(|w| w.set_sq(13, channel.channel())), - Sequence::Fifteen => T::regs().sqr1().modify(|w| w.set_sq(14, channel.channel())), - Sequence::Sixteen => T::regs().sqr1().modify(|w| w.set_sq(15, channel.channel())), + Sequence::Seven => T::regs().sqr2().modify(|w| w.set_sq(0, channel.channel())), + Sequence::Eight => T::regs().sqr2().modify(|w| w.set_sq(1, channel.channel())), + Sequence::Nine => T::regs().sqr2().modify(|w| w.set_sq(2, channel.channel())), + Sequence::Ten => T::regs().sqr2().modify(|w| w.set_sq(3, channel.channel())), + Sequence::Eleven => T::regs().sqr2().modify(|w| w.set_sq(4, channel.channel())), + Sequence::Twelve => T::regs().sqr2().modify(|w| w.set_sq(5, channel.channel())), + Sequence::Thirteen => T::regs().sqr1().modify(|w| w.set_sq(0, channel.channel())), + Sequence::Fourteen => T::regs().sqr1().modify(|w| w.set_sq(1, channel.channel())), + Sequence::Fifteen => T::regs().sqr1().modify(|w| w.set_sq(2, channel.channel())), + Sequence::Sixteen => T::regs().sqr1().modify(|w| w.set_sq(3, channel.channel())), }; if !was_on { @@ -226,9 +222,8 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// Turns on ADC if it is not already turned on and starts continuous DMA transfer. pub fn start(&mut self) -> Result<(), OverrunError> { - self.ring_buf.clear(); - self.setup_adc(); + self.ring_buf.clear(); Ok(()) } @@ -245,7 +240,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { /// [`start`]: #method.start pub fn teardown_adc(&mut self) { // Stop the DMA transfer - self.ring_buf.request_stop(); + self.ring_buf.request_pause(); let r = T::regs(); @@ -311,7 +306,7 @@ impl<'d, T: Instance> RingBufferedAdc<'d, T> { // DMA requests are issues as long as DMA=1 and data are converted. w.set_dds(vals::Dds::CONTINUOUS); // EOC flag is set at the end of each conversion. - w.set_eocs(vals::Eocs::EACHCONVERSION); + w.set_eocs(vals::Eocs::EACH_CONVERSION); }); // Begin ADC conversions diff --git a/embassy-stm32/src/adc/u5_adc4.rs b/embassy-stm32/src/adc/u5_adc4.rs new file mode 100644 index 000000000..1dd664366 --- /dev/null +++ b/embassy-stm32/src/adc/u5_adc4.rs @@ -0,0 +1,478 @@ +#[allow(unused)] +use pac::adc::vals::{Adc4Dmacfg, Adc4Exten, Adc4OversamplingRatio}; + +use super::{blocking_delay_us, AdcChannel, AnyAdcChannel, RxDma4, SealedAdcChannel}; +use crate::dma::Transfer; +pub use crate::pac::adc::regs::Adc4Chselrmod0; +pub use crate::pac::adc::vals::{Adc4Presc as Presc, Adc4Res as Resolution, Adc4SampleTime as SampleTime}; +use crate::time::Hertz; +use crate::{pac, rcc, Peri}; + +const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(55); + +/// Default VREF voltage used for sample conversion to millivolts. +pub const VREF_DEFAULT_MV: u32 = 3300; +/// VREF voltage used for factory calibration of VREFINTCAL register. +pub const VREF_CALIB_MV: u32 = 3300; + +const VREF_CHANNEL: u8 = 0; +const VCORE_CHANNEL: u8 = 12; +const TEMP_CHANNEL: u8 = 13; +const VBAT_CHANNEL: u8 = 14; +const DAC_CHANNEL: u8 = 21; + +// NOTE: Vrefint/Temperature/Vbat are not available on all ADCs, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs +/// Internal voltage reference channel. +pub struct VrefInt; +impl AdcChannel for VrefInt {} +impl SealedAdcChannel for VrefInt { + fn channel(&self) -> u8 { + VREF_CHANNEL + } +} + +/// Internal temperature channel. +pub struct Temperature; +impl AdcChannel for Temperature {} +impl SealedAdcChannel for Temperature { + fn channel(&self) -> u8 { + TEMP_CHANNEL + } +} + +/// Internal battery voltage channel. +pub struct Vbat; +impl AdcChannel for Vbat {} +impl SealedAdcChannel for Vbat { + fn channel(&self) -> u8 { + VBAT_CHANNEL + } +} + +/// Internal DAC channel. +pub struct Dac; +impl AdcChannel for Dac {} +impl SealedAdcChannel for Dac { + fn channel(&self) -> u8 { + DAC_CHANNEL + } +} + +/// Internal Vcore channel. +pub struct Vcore; +impl AdcChannel for Vcore {} +impl SealedAdcChannel for Vcore { + fn channel(&self) -> u8 { + VCORE_CHANNEL + } +} + +pub enum DacChannel { + OUT1, + OUT2, +} + +/// Number of samples used for averaging. +pub enum Averaging { + Disabled, + Samples2, + Samples4, + Samples8, + Samples16, + Samples32, + Samples64, + Samples128, + Samples256, +} + +pub const fn resolution_to_max_count(res: Resolution) -> u32 { + match res { + Resolution::BITS12 => (1 << 12) - 1, + Resolution::BITS10 => (1 << 10) - 1, + Resolution::BITS8 => (1 << 8) - 1, + Resolution::BITS6 => (1 << 6) - 1, + #[allow(unreachable_patterns)] + _ => core::unreachable!(), + } +} + +// NOTE (unused): The prescaler enum closely copies the hardware capabilities, +// but high prescaling doesn't make a lot of sense in the current implementation and is ommited. +#[allow(unused)] +enum Prescaler { + NotDivided, + DividedBy2, + DividedBy4, + DividedBy6, + DividedBy8, + DividedBy10, + DividedBy12, + DividedBy16, + DividedBy32, + DividedBy64, + DividedBy128, + DividedBy256, +} + +impl Prescaler { + fn from_ker_ck(frequency: Hertz) -> Self { + let raw_prescaler = frequency.0 / MAX_ADC_CLK_FREQ.0; + match raw_prescaler { + 0 => Self::NotDivided, + 1 => Self::DividedBy2, + 2..=3 => Self::DividedBy4, + 4..=5 => Self::DividedBy6, + 6..=7 => Self::DividedBy8, + 8..=9 => Self::DividedBy10, + 10..=11 => Self::DividedBy12, + _ => unimplemented!(), + } + } + + fn divisor(&self) -> u32 { + match self { + Prescaler::NotDivided => 1, + Prescaler::DividedBy2 => 2, + Prescaler::DividedBy4 => 4, + Prescaler::DividedBy6 => 6, + Prescaler::DividedBy8 => 8, + Prescaler::DividedBy10 => 10, + Prescaler::DividedBy12 => 12, + Prescaler::DividedBy16 => 16, + Prescaler::DividedBy32 => 32, + Prescaler::DividedBy64 => 64, + Prescaler::DividedBy128 => 128, + Prescaler::DividedBy256 => 256, + } + } + + fn presc(&self) -> Presc { + match self { + Prescaler::NotDivided => Presc::DIV1, + Prescaler::DividedBy2 => Presc::DIV2, + Prescaler::DividedBy4 => Presc::DIV4, + Prescaler::DividedBy6 => Presc::DIV6, + Prescaler::DividedBy8 => Presc::DIV8, + Prescaler::DividedBy10 => Presc::DIV10, + Prescaler::DividedBy12 => Presc::DIV12, + Prescaler::DividedBy16 => Presc::DIV16, + Prescaler::DividedBy32 => Presc::DIV32, + Prescaler::DividedBy64 => Presc::DIV64, + Prescaler::DividedBy128 => Presc::DIV128, + Prescaler::DividedBy256 => Presc::DIV256, + } + } +} + +pub trait SealedInstance { + #[allow(unused)] + fn regs() -> crate::pac::adc::Adc4; +} + +pub trait Instance: SealedInstance + crate::PeripheralType + crate::rcc::RccPeripheral { + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +pub struct Adc4<'d, T: Instance> { + #[allow(unused)] + adc: crate::Peri<'d, T>, +} + +#[derive(Debug)] +pub enum Adc4Error { + InvalidSequence, + DMAError, +} + +impl<'d, T: Instance> Adc4<'d, T> { + /// Create a new ADC driver. + pub fn new(adc: Peri<'d, T>) -> Self { + rcc::enable_and_reset::(); + let prescaler = Prescaler::from_ker_ck(T::frequency()); + + T::regs().ccr().modify(|w| w.set_presc(prescaler.presc())); + + let frequency = Hertz(T::frequency().0 / prescaler.divisor()); + info!("ADC4 frequency set to {}", frequency); + + if frequency > MAX_ADC_CLK_FREQ { + panic!("Maximal allowed frequency for ADC4 is {} MHz and it varies with different packages, refer to ST docs for more information.", MAX_ADC_CLK_FREQ.0 / 1_000_000 ); + } + + let mut s = Self { adc }; + + s.power_up(); + + s.calibrate(); + blocking_delay_us(1); + + s.enable(); + s.configure(); + + s + } + + fn power_up(&mut self) { + T::regs().isr().modify(|w| { + w.set_ldordy(true); + }); + T::regs().cr().modify(|w| { + w.set_advregen(true); + }); + while !T::regs().isr().read().ldordy() {} + + T::regs().isr().modify(|w| { + w.set_ldordy(true); + }); + } + + fn calibrate(&mut self) { + T::regs().cr().modify(|w| w.set_adcal(true)); + while T::regs().cr().read().adcal() {} + T::regs().isr().modify(|w| w.set_eocal(true)); + } + + fn enable(&mut self) { + T::regs().isr().write(|w| w.set_adrdy(true)); + T::regs().cr().modify(|w| w.set_aden(true)); + while !T::regs().isr().read().adrdy() {} + T::regs().isr().write(|w| w.set_adrdy(true)); + } + + fn configure(&mut self) { + // single conversion mode, software trigger + T::regs().cfgr1().modify(|w| { + w.set_cont(false); + w.set_discen(false); + w.set_exten(Adc4Exten::DISABLED); + w.set_chselrmod(false); + }); + + // only use one channel at the moment + T::regs().smpr().modify(|w| { + for i in 0..24 { + w.set_smpsel(i, false); + } + }); + } + + /// Enable reading the voltage reference internal channel. + pub fn enable_vrefint(&self) -> VrefInt { + T::regs().ccr().modify(|w| { + w.set_vrefen(true); + }); + + VrefInt {} + } + + /// Enable reading the temperature internal channel. + pub fn enable_temperature(&self) -> Temperature { + T::regs().ccr().modify(|w| { + w.set_vsensesel(true); + }); + + Temperature {} + } + + /// Enable reading the vbat internal channel. + pub fn enable_vbat(&self) -> Vbat { + T::regs().ccr().modify(|w| { + w.set_vbaten(true); + }); + + Vbat {} + } + + /// Enable reading the vbat internal channel. + pub fn enable_vcore(&self) -> Vcore { + Vcore {} + } + + /// Enable reading the vbat internal channel. + pub fn enable_dac_channel(&self, dac: DacChannel) -> Dac { + let mux; + match dac { + DacChannel::OUT1 => mux = false, + DacChannel::OUT2 => mux = true, + } + T::regs().or().modify(|w| w.set_chn21sel(mux)); + Dac {} + } + + /// Set the ADC sample time. + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + T::regs().smpr().modify(|w| { + w.set_smp(0, sample_time); + }); + } + + /// Get the ADC sample time. + pub fn sample_time(&self) -> SampleTime { + T::regs().smpr().read().smp(0) + } + + /// Set the ADC resolution. + pub fn set_resolution(&mut self, resolution: Resolution) { + T::regs().cfgr1().modify(|w| w.set_res(resolution.into())); + } + + /// Set hardware averaging. + pub fn set_averaging(&mut self, averaging: Averaging) { + let (enable, samples, right_shift) = match averaging { + Averaging::Disabled => (false, Adc4OversamplingRatio::OVERSAMPLE2X, 0), + Averaging::Samples2 => (true, Adc4OversamplingRatio::OVERSAMPLE2X, 1), + Averaging::Samples4 => (true, Adc4OversamplingRatio::OVERSAMPLE4X, 2), + Averaging::Samples8 => (true, Adc4OversamplingRatio::OVERSAMPLE8X, 3), + Averaging::Samples16 => (true, Adc4OversamplingRatio::OVERSAMPLE16X, 4), + Averaging::Samples32 => (true, Adc4OversamplingRatio::OVERSAMPLE32X, 5), + Averaging::Samples64 => (true, Adc4OversamplingRatio::OVERSAMPLE64X, 6), + Averaging::Samples128 => (true, Adc4OversamplingRatio::OVERSAMPLE128X, 7), + Averaging::Samples256 => (true, Adc4OversamplingRatio::OVERSAMPLE256X, 8), + }; + + T::regs().cfgr2().modify(|w| { + w.set_ovsr(samples); + w.set_ovss(right_shift); + w.set_ovse(enable) + }) + } + + /// Read an ADC channel. + pub fn blocking_read(&mut self, channel: &mut impl AdcChannel) -> u16 { + channel.setup(); + + // Select channel + T::regs().chselrmod0().write_value(Adc4Chselrmod0(0_u32)); + T::regs().chselrmod0().modify(|w| { + w.set_chsel(channel.channel() as usize, true); + }); + + // Reset interrupts + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + while !T::regs().isr().read().eos() { + // spin + } + + T::regs().dr().read().0 as u16 + } + + /// Read one or multiple ADC channels using DMA. + /// + /// `sequence` iterator and `readings` must have the same length. + /// The channels in `sequence` must be in ascending order. + /// + /// Example + /// ```rust,ignore + /// use embassy_stm32::adc::adc4; + /// use embassy_stm32::adc::AdcChannel; + /// + /// let mut adc4 = adc4::Adc4::new(p.ADC4); + /// let mut adc4_pin1 = p.PC1; + /// let mut adc4_pin2 = p.PC0; + /// let mut.into()d41 = adc4_pin1.into(); + /// let mut.into()d42 = adc4_pin2.into(); + /// let mut measurements = [0u16; 2]; + /// // not that the channels must be in ascending order + /// adc4.read( + /// &mut p.GPDMA1_CH1, + /// [ + /// &mut.into()d42, + /// &mut.into()d41, + /// ] + /// .into_iter(), + /// &mut measurements, + /// ).await.unwrap(); + /// ``` + pub async fn read( + &mut self, + rx_dma: Peri<'_, impl RxDma4>, + sequence: impl ExactSizeIterator>, + readings: &mut [u16], + ) -> Result<(), Adc4Error> { + assert!(sequence.len() != 0, "Asynchronous read sequence cannot be empty"); + assert!( + sequence.len() == readings.len(), + "Sequence length must be equal to readings length" + ); + + // Ensure no conversions are ongoing + Self::cancel_conversions(); + + T::regs().isr().modify(|reg| { + reg.set_ovr(true); + reg.set_eos(true); + reg.set_eoc(true); + }); + + T::regs().cfgr1().modify(|reg| { + reg.set_dmaen(true); + reg.set_dmacfg(Adc4Dmacfg::ONE_SHOT); + reg.set_chselrmod(false); + }); + + // Verify and activate sequence + let mut prev_channel: i16 = -1; + T::regs().chselrmod0().write_value(Adc4Chselrmod0(0_u32)); + for channel in sequence { + let channel_num = channel.channel; + if channel_num as i16 <= prev_channel { + return Err(Adc4Error::InvalidSequence); + }; + prev_channel = channel_num as i16; + + T::regs().chselrmod0().modify(|w| { + w.set_chsel(channel.channel as usize, true); + }); + } + + let request = rx_dma.request(); + let transfer = unsafe { + Transfer::new_read( + rx_dma, + request, + T::regs().dr().as_ptr() as *mut u16, + readings, + Default::default(), + ) + }; + + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); + + transfer.await; + + // Ensure conversions are finished. + Self::cancel_conversions(); + + // Reset configuration. + T::regs().cfgr1().modify(|reg| { + reg.set_dmaen(false); + }); + + if T::regs().isr().read().ovr() { + Err(Adc4Error::DMAError) + } else { + Ok(()) + } + } + + fn cancel_conversions() { + if T::regs().cr().read().adstart() && !T::regs().cr().read().addis() { + T::regs().cr().modify(|reg| { + reg.set_adstp(true); + }); + while T::regs().cr().read().adstart() {} + } + } +} diff --git a/embassy-stm32/src/adc/v1.rs b/embassy-stm32/src/adc/v1.rs index 9bec2e13b..fb6f5b7d0 100644 --- a/embassy-stm32/src/adc/v1.rs +++ b/embassy-stm32/src/adc/v1.rs @@ -2,7 +2,6 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::into_ref; #[cfg(adc_l0)] use stm32_metapac::adc::vals::Ckmode; @@ -10,7 +9,7 @@ use super::blocking_delay_us; use crate::adc::{Adc, AdcChannel, Instance, Resolution, SampleTime}; use crate::interrupt::typelevel::Interrupt; use crate::peripherals::ADC1; -use crate::{interrupt, rcc, Peripheral}; +use crate::{interrupt, rcc, Peri}; pub const VDDA_CALIB_MV: u32 = 3300; pub const VREF_INT: u32 = 1230; @@ -63,10 +62,9 @@ impl super::SealedAdcChannel for Temperature { impl<'d, T: Instance> Adc<'d, T> { pub fn new( - adc: impl Peripheral

+ 'd, + adc: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { - into_ref!(adc); rcc::enable_and_reset::(); // Delay 1μs when using HSI14 as the ADC clock. @@ -160,7 +158,7 @@ impl<'d, T: Instance> Adc<'d, T> { channel.setup(); // A.7.5 Single conversion sequence code example - Software trigger - T::regs().chselr().write(|reg| reg.set_chselx(ch_num as usize, true)); + T::regs().chselr().write(|reg| reg.set_chsel_x(ch_num as usize, true)); self.convert().await } diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index 842a5ee6d..e94a25b24 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -1,10 +1,8 @@ -use embassy_hal_internal::into_ref; - use super::blocking_delay_us; use crate::adc::{Adc, AdcChannel, Instance, Resolution, SampleTime}; use crate::peripherals::ADC1; use crate::time::Hertz; -use crate::{rcc, Peripheral}; +use crate::{rcc, Peri}; mod ringbuffered_v2; pub use ringbuffered_v2::{RingBufferedAdc, Sequence}; @@ -97,8 +95,7 @@ impl<'d, T> Adc<'d, T> where T: Instance, { - pub fn new(adc: impl Peripheral

+ 'd) -> Self { - into_ref!(adc); + pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); let presc = Prescaler::from_pclk2(T::frequency()); diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 9441e42ff..313244e19 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,12 +1,11 @@ use cfg_if::cfg_if; -use embassy_hal_internal::into_ref; use pac::adc::vals::Dmacfg; use super::{ blocking_delay_us, Adc, AdcChannel, AnyAdcChannel, Instance, Resolution, RxDma, SampleTime, SealedAdcChannel, }; use crate::dma::Transfer; -use crate::{pac, rcc, Peripheral}; +use crate::{pac, rcc, Peri}; /// Default VREF voltage used for sample conversion to millivolts. pub const VREF_DEFAULT_MV: u32 = 3300; @@ -20,7 +19,7 @@ impl SealedAdcChannel for VrefInt { cfg_if! { if #[cfg(adc_g0)] { let val = 13; - } else if #[cfg(adc_h5)] { + } else if #[cfg(any(adc_h5, adc_h7rs))] { let val = 17; } else if #[cfg(adc_u0)] { let val = 12; @@ -39,7 +38,7 @@ impl SealedAdcChannel for Temperature { cfg_if! { if #[cfg(adc_g0)] { let val = 12; - } else if #[cfg(adc_h5)] { + } else if #[cfg(any(adc_h5, adc_h7rs))] { let val = 16; } else if #[cfg(adc_u0)] { let val = 11; @@ -58,9 +57,9 @@ impl SealedAdcChannel for Vbat { cfg_if! { if #[cfg(adc_g0)] { let val = 14; - } else if #[cfg(adc_h5)] { + } else if #[cfg(any(adc_h5, adc_h7rs))] { let val = 2; - } else if #[cfg(adc_h5)] { + } else if #[cfg(any(adc_h5, adc_h7rs))] { let val = 13; } else { let val = 18; @@ -71,7 +70,7 @@ impl SealedAdcChannel for Vbat { } cfg_if! { - if #[cfg(adc_h5)] { + if #[cfg(any(adc_h5, adc_h7rs))] { pub struct VddCore; impl AdcChannel for VddCore {} impl super::SealedAdcChannel for VddCore { @@ -95,8 +94,7 @@ cfg_if! { } impl<'d, T: Instance> Adc<'d, T> { - pub fn new(adc: impl Peripheral

+ 'd) -> Self { - into_ref!(adc); + pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); T::regs().cr().modify(|reg| { #[cfg(not(any(adc_g0, adc_u0)))] @@ -173,7 +171,7 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().ccr().modify(|reg| { reg.set_tsen(true); }); - } else if #[cfg(adc_h5)] { + } else if #[cfg(any(adc_h5, adc_h7rs))] { T::common_regs().ccr().modify(|reg| { reg.set_tsen(true); }); @@ -193,7 +191,7 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().ccr().modify(|reg| { reg.set_vbaten(true); }); - } else if #[cfg(adc_h5)] { + } else if #[cfg(any(adc_h5, adc_h7rs))] { T::common_regs().ccr().modify(|reg| { reg.set_vbaten(true); }); @@ -262,6 +260,9 @@ impl<'d, T: Instance> Adc<'d, T> { /// /// `sequence` iterator and `readings` must have the same length. /// + /// Note: The order of values in `readings` is defined by the pin ADC + /// channel number and not the pin order in `sequence`. + /// /// Example /// ```rust,ignore /// use embassy_stm32::adc::{Adc, AdcChannel} @@ -271,8 +272,8 @@ impl<'d, T: Instance> Adc<'d, T> { /// let mut adc_pin1 = p.PA1.degrade_adc(); /// let mut measurements = [0u16; 2]; /// - /// adc.read_async( - /// p.DMA1_CH2, + /// adc.read( + /// p.DMA1_CH2.reborrow(), /// [ /// (&mut *adc_pin0, SampleTime::CYCLES160_5), /// (&mut *adc_pin1, SampleTime::CYCLES160_5), @@ -285,7 +286,7 @@ impl<'d, T: Instance> Adc<'d, T> { /// ``` pub async fn read( &mut self, - rx_dma: &mut impl RxDma, + rx_dma: Peri<'_, impl RxDma>, sequence: impl ExactSizeIterator, SampleTime)>, readings: &mut [u16], ) { @@ -366,14 +367,14 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().cfgr().modify(|reg| { reg.set_discen(false); reg.set_cont(true); - reg.set_dmacfg(Dmacfg::ONESHOT); + reg.set_dmacfg(Dmacfg::ONE_SHOT); reg.set_dmaen(true); }); #[cfg(any(adc_g0, adc_u0))] T::regs().cfgr1().modify(|reg| { reg.set_discen(false); reg.set_cont(true); - reg.set_dmacfg(Dmacfg::ONESHOT); + reg.set_dmacfg(Dmacfg::ONE_SHOT); reg.set_dmaen(true); }); @@ -413,7 +414,7 @@ impl<'d, T: Instance> Adc<'d, T> { fn configure_channel(channel: &mut impl AdcChannel, sample_time: SampleTime) { // RM0492, RM0481, etc. // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected." - #[cfg(adc_h5)] + #[cfg(any(adc_h5, adc_h7rs))] if channel.channel() == 0 { T::regs().or().modify(|reg| reg.set_op0(true)); } @@ -446,7 +447,7 @@ impl<'d, T: Instance> Adc<'d, T> { // RM0492, RM0481, etc. // "This option bit must be set to 1 when ADCx_INP0 or ADCx_INN1 channel is selected." - #[cfg(adc_h5)] + #[cfg(any(adc_h5, adc_h7rs))] if channel.channel() == 0 { T::regs().or().modify(|reg| reg.set_op0(false)); } @@ -474,7 +475,7 @@ impl<'d, T: Instance> Adc<'d, T> { if #[cfg(any(adc_g0, adc_u0))] { // On G0 and U6 all channels use the same sampling time. T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into())); - } else if #[cfg(adc_h5)] { + } else if #[cfg(any(adc_h5, adc_h7rs))] { 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())), diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index 63b5b58ea..39e0d51b9 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -1,5 +1,7 @@ +#[cfg(not(stm32u5))] +use pac::adc::vals::{Adcaldif, Boost}; #[allow(unused)] -use pac::adc::vals::{Adcaldif, Adstp, Boost, Difsel, Dmngt, Exten, Pcsel}; +use pac::adc::vals::{Adstp, Difsel, Dmngt, Exten, Pcsel}; use pac::adccommon::vals::Presc; use super::{ @@ -7,7 +9,7 @@ use super::{ }; use crate::dma::Transfer; use crate::time::Hertz; -use crate::{pac, rcc, Peripheral}; +use crate::{pac, rcc, Peri}; /// Default VREF voltage used for sample conversion to millivolts. pub const VREF_DEFAULT_MV: u32 = 3300; @@ -19,6 +21,8 @@ pub const VREF_CALIB_MV: u32 = 3300; const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(60); #[cfg(stm32h7)] const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(50); +#[cfg(stm32u5)] +const MAX_ADC_CLK_FREQ: Hertz = Hertz::mhz(55); #[cfg(stm32g4)] const VREF_CHANNEL: u8 = 18; @@ -31,8 +35,16 @@ const VREF_CHANNEL: u8 = 19; const TEMP_CHANNEL: u8 = 18; // TODO this should be 14 for H7a/b/35 +#[cfg(not(stm32u5))] const VBAT_CHANNEL: u8 = 17; +#[cfg(stm32u5)] +const VREF_CHANNEL: u8 = 0; +#[cfg(stm32u5)] +const TEMP_CHANNEL: u8 = 19; +#[cfg(stm32u5)] +const VBAT_CHANNEL: u8 = 18; + // NOTE: Vrefint/Temperature/Vbat are not available on all ADCs, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs /// Internal voltage reference channel. pub struct VrefInt; @@ -146,8 +158,7 @@ pub enum Averaging { impl<'d, T: Instance> Adc<'d, T> { /// Create a new ADC driver. - pub fn new(adc: impl Peripheral

+ 'd) -> Self { - embassy_hal_internal::into_ref!(adc); + pub fn new(adc: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); let prescaler = Prescaler::from_ker_ck(T::frequency()); @@ -155,7 +166,7 @@ impl<'d, T: Instance> Adc<'d, T> { T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); let frequency = Hertz(T::frequency().0 / prescaler.divisor()); - info!("ADC frequency set to {} Hz", frequency.0); + info!("ADC frequency set to {}", frequency); if frequency > MAX_ADC_CLK_FREQ { panic!("Maximal allowed frequency for the ADC is {} MHz and it varies with different packages, refer to ST docs for more information.", MAX_ADC_CLK_FREQ.0 / 1_000_000 ); @@ -202,14 +213,15 @@ impl<'d, T: Instance> Adc<'d, T> { fn configure_differential_inputs(&mut self) { T::regs().difsel().modify(|w| { for n in 0..20 { - w.set_difsel(n, Difsel::SINGLEENDED); + w.set_difsel(n, Difsel::SINGLE_ENDED); } }); } fn calibrate(&mut self) { T::regs().cr().modify(|w| { - w.set_adcaldif(Adcaldif::SINGLEENDED); + #[cfg(not(adc_u5))] + w.set_adcaldif(Adcaldif::SINGLE_ENDED); w.set_adcallin(true); }); @@ -293,7 +305,7 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().cfgr2().modify(|reg| { reg.set_rovse(enable); - reg.set_osvr(samples); + reg.set_ovsr(samples); reg.set_ovss(right_shift); }) } @@ -331,12 +343,12 @@ impl<'d, T: Instance> Adc<'d, T> { /// use embassy_stm32::adc::{Adc, AdcChannel} /// /// let mut adc = Adc::new(p.ADC1); - /// let mut adc_pin0 = p.PA0.degrade_adc(); - /// let mut adc_pin2 = p.PA2.degrade_adc(); + /// let mut adc_pin0 = p.PA0.into(); + /// let mut adc_pin2 = p.PA2.into(); /// let mut measurements = [0u16; 2]; /// - /// adc.read_async( - /// p.DMA2_CH0, + /// adc.read( + /// p.DMA2_CH0.reborrow(), /// [ /// (&mut *adc_pin0, SampleTime::CYCLES112), /// (&mut *adc_pin2, SampleTime::CYCLES112), @@ -349,7 +361,7 @@ impl<'d, T: Instance> Adc<'d, T> { /// ``` pub async fn read( &mut self, - rx_dma: &mut impl RxDma, + rx_dma: Peri<'_, impl RxDma>, sequence: impl ExactSizeIterator, SampleTime)>, readings: &mut [u16], ) { @@ -407,7 +419,7 @@ impl<'d, T: Instance> Adc<'d, T> { }); T::regs().cfgr().modify(|reg| { reg.set_cont(true); - reg.set_dmngt(Dmngt::DMA_ONESHOT); + reg.set_dmngt(Dmngt::DMA_ONE_SHOT); }); let request = rx_dma.request(); @@ -446,7 +458,7 @@ impl<'d, T: Instance> Adc<'d, T> { Self::set_channel_sample_time(channel, sample_time); - #[cfg(stm32h7)] + #[cfg(any(stm32h7, stm32u5))] { T::regs().cfgr2().modify(|w| w.set_lshift(0)); T::regs() diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs index 278c93ff4..305666d5b 100644 --- a/embassy-stm32/src/can/bxcan/mod.rs +++ b/embassy-stm32/src/can/bxcan/mod.rs @@ -6,7 +6,7 @@ use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::interrupt::InterruptExt; -use embassy_hal_internal::into_ref; +use embassy_hal_internal::PeripheralType; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; use embassy_sync::waitqueue::AtomicWaker; @@ -17,11 +17,11 @@ use self::registers::{Registers, RxFifo}; pub use super::common::{BufferedCanReceiver, BufferedCanSender}; use super::frame::{Envelope, Frame}; use super::util; -use crate::can::enums::{BusError, TryReadError}; +use crate::can::enums::{BusError, InternalOperation, TryReadError}; use crate::gpio::{AfType, OutputType, Pull, Speed}; use crate::interrupt::typelevel::Interrupt; use crate::rcc::{self, RccPeripheral}; -use crate::{interrupt, peripherals, Peripheral}; +use crate::{interrupt, peripherals, Peri}; /// Interrupt handler. pub struct TxInterruptHandler { @@ -68,7 +68,6 @@ pub struct SceInterruptHandler { impl interrupt::typelevel::Handler for SceInterruptHandler { unsafe fn on_interrupt() { - info!("sce irq"); let msr = T::regs().msr(); let msr_val = msr.read(); @@ -76,9 +75,8 @@ impl interrupt::typelevel::Handler for SceInterrup msr.modify(|m| m.set_slaki(true)); T::state().err_waker.wake(); } else if msr_val.erri() { - info!("Error interrupt"); // Disable the interrupt, but don't acknowledge the error, so that it can be - // forwarded off the the bus message consumer. If we don't provide some way for + // forwarded off the bus message consumer. If we don't provide some way for // downstream code to determine that it has already provided this bus error instance // to the bus message consumer, we are doomed to re-provide a single error instance for // an indefinite amount of time. @@ -175,16 +173,15 @@ impl<'d> Can<'d> { /// Creates a new Bxcan instance, keeping the peripheral in sleep mode. /// You must call [Can::enable_non_blocking] to use the peripheral. pub fn new( - _peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, + _peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, _irqs: impl interrupt::typelevel::Binding> + interrupt::typelevel::Binding> + interrupt::typelevel::Binding> + interrupt::typelevel::Binding> + 'd, ) -> Self { - into_ref!(_peri, rx, tx); let info = T::info(); let regs = &T::info().regs; @@ -504,6 +501,14 @@ impl<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, TX_ pub fn reader(&self) -> BufferedCanReceiver { self.rx.reader() } + + /// Accesses the filter banks owned by this CAN peripheral. + /// + /// To modify filters of a slave peripheral, `modify_filters` has to be called on the master + /// peripheral instead. + pub fn modify_filters(&mut self) -> MasterFilters<'_> { + self.rx.modify_filters() + } } /// CAN driver, transmit half. @@ -679,22 +684,18 @@ impl<'d, const TX_BUF_SIZE: usize> BufferedCanTx<'d, TX_BUF_SIZE> { /// Returns a sender that can be used for sending CAN frames. pub fn writer(&self) -> BufferedCanSender { + (self.info.internal_operation)(InternalOperation::NotifySenderCreated); BufferedCanSender { tx_buf: self.tx_buf.sender().into(), waker: self.info.tx_waker, + internal_operation: self.info.internal_operation, } } } impl<'d, const TX_BUF_SIZE: usize> Drop for BufferedCanTx<'d, TX_BUF_SIZE> { fn drop(&mut self) { - critical_section::with(|_| { - let state = self.state as *const State; - unsafe { - let mut_state = state as *mut State; - (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); - } - }); + (self.info.internal_operation)(InternalOperation::NotifySenderDestroyed); } } @@ -735,6 +736,14 @@ impl<'d> CanRx<'d> { ) -> BufferedCanRx<'d, RX_BUF_SIZE> { BufferedCanRx::new(self.info, self.state, self, rxb) } + + /// Accesses the filter banks owned by this CAN peripheral. + /// + /// To modify filters of a slave peripheral, `modify_filters` has to be called on the master + /// peripheral instead. + pub fn modify_filters(&mut self) -> MasterFilters<'_> { + unsafe { MasterFilters::new(self.info) } + } } /// User supplied buffer for RX Buffering @@ -744,16 +753,16 @@ pub type RxBuf = Channel { info: &'static Info, state: &'static State, - _rx: CanRx<'d>, + rx: CanRx<'d>, rx_buf: &'static RxBuf, } impl<'d, const RX_BUF_SIZE: usize> BufferedCanRx<'d, RX_BUF_SIZE> { - fn new(info: &'static Info, state: &'static State, _rx: CanRx<'d>, rx_buf: &'static RxBuf) -> Self { + fn new(info: &'static Info, state: &'static State, rx: CanRx<'d>, rx_buf: &'static RxBuf) -> Self { BufferedCanRx { info, state, - _rx, + rx, rx_buf, } .setup() @@ -811,19 +820,25 @@ impl<'d, const RX_BUF_SIZE: usize> BufferedCanRx<'d, RX_BUF_SIZE> { /// 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() + (self.info.internal_operation)(InternalOperation::NotifyReceiverCreated); + BufferedCanReceiver { + rx_buf: self.rx_buf.receiver().into(), + internal_operation: self.info.internal_operation, + } + } + + /// Accesses the filter banks owned by this CAN peripheral. + /// + /// To modify filters of a slave peripheral, `modify_filters` has to be called on the master + /// peripheral instead. + pub fn modify_filters(&mut self) -> MasterFilters<'_> { + self.rx.modify_filters() } } impl<'d, const RX_BUF_SIZE: usize> Drop for BufferedCanRx<'d, RX_BUF_SIZE> { fn drop(&mut self) { - critical_section::with(|_| { - let state = self.state as *const State; - unsafe { - let mut_state = state as *mut State; - (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); - } - }); + (self.info.internal_operation)(InternalOperation::NotifyReceiverDestroyed); } } @@ -895,7 +910,7 @@ impl RxMode { RxFifo::Fifo0 => 0usize, RxFifo::Fifo1 => 1usize, }; - T::regs().ier().write(|w| { + T::regs().ier().modify(|w| { w.set_fmpie(fifo_idx, false); }); waker.wake(); @@ -938,18 +953,22 @@ impl RxMode { Self::NonBuffered(_) => { let registers = &info.regs; if let Some(msg) = registers.receive_fifo(RxFifo::Fifo0) { - registers.0.ier().write(|w| { + registers.0.ier().modify(|w| { w.set_fmpie(0, true); }); Ok(msg) } else if let Some(msg) = registers.receive_fifo(RxFifo::Fifo1) { - registers.0.ier().write(|w| { + registers.0.ier().modify(|w| { w.set_fmpie(1, true); }); Ok(msg) } else if let Some(err) = registers.curr_error() { Err(TryReadError::BusError(err)) } else { + registers.0.ier().modify(|w| { + w.set_fmpie(0, true); + w.set_fmpie(1, true); + }); Err(TryReadError::Empty) } } @@ -1022,6 +1041,8 @@ pub(crate) struct State { pub(crate) rx_mode: RxMode, pub(crate) tx_mode: TxMode, pub err_waker: AtomicWaker, + receiver_instance_count: usize, + sender_instance_count: usize, } impl State { @@ -1030,6 +1051,8 @@ impl State { rx_mode: RxMode::NonBuffered(AtomicWaker::new()), tx_mode: TxMode::NonBuffered(AtomicWaker::new()), err_waker: AtomicWaker::new(), + receiver_instance_count: 1, + sender_instance_count: 1, } } } @@ -1041,6 +1064,7 @@ pub(crate) struct Info { rx1_interrupt: crate::interrupt::Interrupt, sce_interrupt: crate::interrupt::Interrupt, tx_waker: fn(), + internal_operation: fn(InternalOperation), /// The total number of filter banks available to the instance. /// @@ -1053,11 +1077,12 @@ trait SealedInstance { fn regs() -> crate::pac::can::Can; fn state() -> &'static State; unsafe fn mut_state() -> &'static mut State; + fn internal_operation(val: InternalOperation); } /// CAN instance trait. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral + 'static { +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static { /// TX interrupt for this instance. type TXInterrupt: crate::interrupt::typelevel::Interrupt; /// RX0 interrupt for this instance. @@ -1110,6 +1135,7 @@ foreach_peripheral!( rx1_interrupt: crate::_generated::peripheral_interrupts::$inst::RX1::IRQ, sce_interrupt: crate::_generated::peripheral_interrupts::$inst::SCE::IRQ, tx_waker: crate::_generated::peripheral_interrupts::$inst::TX::pend, + internal_operation: peripherals::$inst::internal_operation, num_filter_banks: peripherals::$inst::NUM_FILTER_BANKS, }; &INFO @@ -1125,6 +1151,37 @@ foreach_peripheral!( fn state() -> &'static State { unsafe { peripherals::$inst::mut_state() } } + + + fn internal_operation(val: InternalOperation) { + critical_section::with(|_| { + //let state = self.state as *const State; + unsafe { + //let mut_state = state as *mut State; + let mut_state = peripherals::$inst::mut_state(); + match val { + InternalOperation::NotifySenderCreated => { + mut_state.sender_instance_count += 1; + } + InternalOperation::NotifySenderDestroyed => { + mut_state.sender_instance_count -= 1; + if ( 0 == mut_state.sender_instance_count) { + (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + } + InternalOperation::NotifyReceiverCreated => { + mut_state.receiver_instance_count += 1; + } + InternalOperation::NotifyReceiverDestroyed => { + mut_state.receiver_instance_count -= 1; + if ( 0 == mut_state.receiver_instance_count) { + (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + } + } + } + }); + } } impl Instance for peripherals::$inst { diff --git a/embassy-stm32/src/can/bxcan/registers.rs b/embassy-stm32/src/can/bxcan/registers.rs index c5de1c683..c295b0f50 100644 --- a/embassy-stm32/src/can/bxcan/registers.rs +++ b/embassy-stm32/src/can/bxcan/registers.rs @@ -2,7 +2,7 @@ use core::cmp::Ordering; use core::convert::Infallible; pub use embedded_can::{ExtendedId, Id, StandardId}; -use stm32_metapac::can::vals::Lec; +use stm32_metapac::can::vals::{Lec, Rtr}; use super::{Mailbox, TransmitStatus}; use crate::can::enums::BusError; @@ -166,16 +166,16 @@ impl Registers { return Some(BusError::BusPassive); } else if err.ewgf() { return Some(BusError::BusWarning); - } else if err.lec() != Lec::NOERROR { + } else if err.lec() != Lec::NO_ERROR { return Some(match err.lec() { Lec::STUFF => BusError::Stuff, Lec::FORM => BusError::Form, Lec::ACK => BusError::Acknowledge, - Lec::BITRECESSIVE => BusError::BitRecessive, - Lec::BITDOMINANT => BusError::BitDominant, + Lec::BIT_RECESSIVE => BusError::BitRecessive, + Lec::BIT_DOMINANT => BusError::BitDominant, Lec::CRC => BusError::Crc, Lec::CUSTOM => BusError::Software, - Lec::NOERROR => unreachable!(), + Lec::NO_ERROR => unreachable!(), }); } None @@ -299,13 +299,16 @@ impl Registers { mb.tdtr().write(|w| w.set_dlc(frame.header().len() as u8)); mb.tdlr() - .write(|w| w.0 = u32::from_ne_bytes(unwrap!(frame.data()[0..4].try_into()))); + .write(|w| w.0 = u32::from_ne_bytes(unwrap!(frame.raw_data()[0..4].try_into()))); mb.tdhr() - .write(|w| w.0 = u32::from_ne_bytes(unwrap!(frame.data()[4..8].try_into()))); + .write(|w| w.0 = u32::from_ne_bytes(unwrap!(frame.raw_data()[4..8].try_into()))); let id: IdReg = frame.id().into(); mb.tir().write(|w| { w.0 = id.0; w.set_txrq(true); + if frame.header().rtr() { + w.set_rtr(Rtr::REMOTE); + } }); } diff --git a/embassy-stm32/src/can/common.rs b/embassy-stm32/src/can/common.rs index a54b54f6e..386d4467c 100644 --- a/embassy-stm32/src/can/common.rs +++ b/embassy-stm32/src/can/common.rs @@ -1,43 +1,43 @@ -use embassy_sync::channel::{DynamicReceiver, DynamicSender}; +use embassy_sync::channel::{SendDynamicReceiver, SendDynamicSender}; use super::enums::*; use super::frame::*; pub(crate) struct ClassicBufferedRxInner { - pub rx_sender: DynamicSender<'static, Result>, + pub rx_sender: SendDynamicSender<'static, Result>, } pub(crate) struct ClassicBufferedTxInner { - pub tx_receiver: DynamicReceiver<'static, Frame>, + pub tx_receiver: SendDynamicReceiver<'static, Frame>, } #[cfg(any(can_fdcan_v1, can_fdcan_h7))] pub(crate) struct FdBufferedRxInner { - pub rx_sender: DynamicSender<'static, Result>, + pub rx_sender: SendDynamicSender<'static, Result>, } #[cfg(any(can_fdcan_v1, can_fdcan_h7))] pub(crate) struct FdBufferedTxInner { - pub tx_receiver: DynamicReceiver<'static, FdFrame>, + pub tx_receiver: SendDynamicReceiver<'static, FdFrame>, } /// Sender that can be used for sending CAN frames. -#[derive(Copy, Clone)] -pub struct BufferedCanSender { - pub(crate) tx_buf: embassy_sync::channel::DynamicSender<'static, Frame>, +pub struct BufferedSender<'ch, FRAME> { + pub(crate) tx_buf: embassy_sync::channel::SendDynamicSender<'ch, FRAME>, pub(crate) waker: fn(), + pub(crate) internal_operation: fn(InternalOperation), } -impl BufferedCanSender { +impl<'ch, FRAME> BufferedSender<'ch, FRAME> { /// Async write frame to TX buffer. - pub fn try_write(&mut self, frame: Frame) -> Result<(), embassy_sync::channel::TrySendError> { + pub fn try_write(&mut self, frame: FRAME) -> Result<(), embassy_sync::channel::TrySendError> { self.tx_buf.try_send(frame)?; (self.waker)(); Ok(()) } /// Async write frame to TX buffer. - pub async fn write(&mut self, frame: Frame) { + pub async fn write(&mut self, frame: FRAME) { self.tx_buf.send(frame).await; (self.waker)(); } @@ -48,5 +48,77 @@ impl BufferedCanSender { } } +impl<'ch, FRAME> Clone for BufferedSender<'ch, FRAME> { + fn clone(&self) -> Self { + (self.internal_operation)(InternalOperation::NotifySenderCreated); + Self { + tx_buf: self.tx_buf, + waker: self.waker, + internal_operation: self.internal_operation, + } + } +} + +impl<'ch, FRAME> Drop for BufferedSender<'ch, FRAME> { + fn drop(&mut self) { + (self.internal_operation)(InternalOperation::NotifySenderDestroyed); + } +} + +/// Sender that can be used for sending Classic CAN frames. +pub type BufferedCanSender = BufferedSender<'static, Frame>; + /// 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>; +pub struct BufferedReceiver<'ch, ENVELOPE> { + pub(crate) rx_buf: embassy_sync::channel::SendDynamicReceiver<'ch, Result>, + pub(crate) internal_operation: fn(InternalOperation), +} + +impl<'ch, ENVELOPE> BufferedReceiver<'ch, ENVELOPE> { + /// Receive the next frame. + /// + /// See [`Channel::receive()`]. + pub fn receive(&self) -> embassy_sync::channel::DynamicReceiveFuture<'_, Result> { + self.rx_buf.receive() + } + + /// Attempt to immediately receive the next frame. + /// + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result, embassy_sync::channel::TryReceiveError> { + self.rx_buf.try_receive() + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll<()> { + self.rx_buf.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next frame + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll> { + self.rx_buf.poll_receive(cx) + } +} + +impl<'ch, ENVELOPE> Clone for BufferedReceiver<'ch, ENVELOPE> { + fn clone(&self) -> Self { + (self.internal_operation)(InternalOperation::NotifyReceiverCreated); + Self { + rx_buf: self.rx_buf, + internal_operation: self.internal_operation, + } + } +} + +impl<'ch, ENVELOPE> Drop for BufferedReceiver<'ch, ENVELOPE> { + fn drop(&mut self) { + (self.internal_operation)(InternalOperation::NotifyReceiverDestroyed); + } +} + +/// A BufferedCanReceiver for Classic CAN frames. +pub type BufferedCanReceiver = BufferedReceiver<'static, Envelope>; diff --git a/embassy-stm32/src/can/enums.rs b/embassy-stm32/src/can/enums.rs index a5cca424d..97cb47640 100644 --- a/embassy-stm32/src/can/enums.rs +++ b/embassy-stm32/src/can/enums.rs @@ -68,3 +68,17 @@ pub enum TryReadError { /// Receive buffer is empty Empty, } + +/// Internal Operation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InternalOperation { + /// Notify receiver created + NotifyReceiverCreated, + /// Notify receiver destroyed + NotifyReceiverDestroyed, + /// Notify sender created + NotifySenderCreated, + /// Notify sender destroyed + NotifySenderDestroyed, +} diff --git a/embassy-stm32/src/can/fd/config.rs b/embassy-stm32/src/can/fd/config.rs index 68161ca50..c6a66b469 100644 --- a/embassy-stm32/src/can/fd/config.rs +++ b/embassy-stm32/src/can/fd/config.rs @@ -328,11 +328,15 @@ pub struct FdCanConfig { /// /// Automatic retransmission is enabled by default. pub automatic_retransmit: bool, - /// Enabled or disables the pausing between transmissions + /// The transmit pause feature is intended for use in CAN systems where the CAN message + /// identifiers are permanently specified to specific values and cannot easily be changed. /// - /// 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. + /// These message identifiers can have a higher CAN arbitration priority than other defined + /// messages, while in a specific application their relative arbitration priority must be inverse. + /// + /// This may lead to a case where one ECU sends a burst of CAN messages that cause + /// another ECU CAN messages to be delayed because that other messages have a lower + /// CAN arbitration priority. pub transmit_pause: bool, /// Enabled or disables the pausing between transmissions /// diff --git a/embassy-stm32/src/can/fd/peripheral.rs b/embassy-stm32/src/can/fd/peripheral.rs index 07e3dddad..0a22f2c91 100644 --- a/embassy-stm32/src/can/fd/peripheral.rs +++ b/embassy-stm32/src/can/fd/peripheral.rs @@ -71,11 +71,28 @@ impl Registers { } } + #[cfg(feature = "time")] + pub fn calc_timestamp(&self, 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"))] + pub fn calc_timestamp(&self, _ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { + ts_val + } + 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]); + put_tx_data(mailbox, buffer); // Set as ready to transmit self.regs.txbar().modify(|w| w.set_ar(bufidx, true)); @@ -190,7 +207,7 @@ impl Registers { DataLength::Fdcan(len) => len, DataLength::Classic(len) => len, }; - if len as usize > ClassicData::MAX_DATA_LEN { + if len as usize > 8 { return None; } @@ -200,7 +217,7 @@ impl Registers { if header_reg.rtr().bit() { F::new_remote(id, len as usize) } else { - F::new(id, &data) + F::new(id, &data[0..(len as usize)]) } } else { // Abort request failed because the frame was already sent (or being sent) on @@ -458,7 +475,7 @@ impl Registers { /// [`FdCanConfig::set_transmit_pause`] #[inline] pub fn set_transmit_pause(&self, enabled: bool) { - self.regs.cccr().modify(|w| w.set_txp(!enabled)); + self.regs.cccr().modify(|w| w.set_txp(enabled)); } /// Configures non-iso mode. See [`FdCanConfig::set_non_iso_mode`] diff --git a/embassy-stm32/src/can/fdcan.rs b/embassy-stm32/src/can/fdcan.rs index c549313f3..97d22315a 100644 --- a/embassy-stm32/src/can/fdcan.rs +++ b/embassy-stm32/src/can/fdcan.rs @@ -4,16 +4,16 @@ use core::marker::PhantomData; use core::task::Poll; use embassy_hal_internal::interrupt::InterruptExt; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; -use embassy_sync::channel::{Channel, DynamicReceiver, DynamicSender}; +use embassy_sync::channel::Channel; use embassy_sync::waitqueue::AtomicWaker; use crate::can::fd::peripheral::Registers; -use crate::gpio::{AfType, OutputType, Pull, Speed}; +use crate::gpio::{AfType, OutputType, Pull, SealedPin as _, Speed}; use crate::interrupt::typelevel::Interrupt; use crate::rcc::{self, RccPeripheral}; -use crate::{interrupt, peripherals, Peripheral}; +use crate::{interrupt, peripherals, Peri}; pub(crate) mod fd; @@ -52,36 +52,39 @@ impl interrupt::typelevel::Handler for IT0Interrup regs.ir().write(|w| w.set_tefn(true)); } - match &T::state().tx_mode { - TxMode::NonBuffered(waker) => waker.wake(), - TxMode::ClassicBuffered(buf) => { - if !T::registers().tx_queue_is_full() { - match buf.tx_receiver.try_receive() { - Ok(frame) => { - _ = T::registers().write(&frame); + T::info().state.lock(|s| { + let state = s.borrow_mut(); + match &state.tx_mode { + TxMode::NonBuffered(waker) => waker.wake(), + TxMode::ClassicBuffered(buf) => { + if !T::registers().tx_queue_is_full() { + match buf.tx_receiver.try_receive() { + Ok(frame) => { + _ = T::registers().write(&frame); + } + Err(_) => {} + } + } + } + TxMode::FdBuffered(buf) => { + if !T::registers().tx_queue_is_full() { + match buf.tx_receiver.try_receive() { + Ok(frame) => { + _ = T::registers().write(&frame); + } + Err(_) => {} } - Err(_) => {} } } } - 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.rfn(0) { - T::state().rx_mode.on_interrupt::(0); - } - if ir.rfn(1) { - T::state().rx_mode.on_interrupt::(1); - } + if ir.rfn(0) { + state.rx_mode.on_interrupt::(0, state.ns_per_timer_tick); + } + if ir.rfn(1) { + state.rx_mode.on_interrupt::(1, state.ns_per_timer_tick); + } + }); if ir.bo() { regs.ir().write(|w| w.set_bo(true)); @@ -165,7 +168,6 @@ pub struct CanConfigurator<'d> { _phantom: PhantomData<&'d ()>, config: crate::can::fd::config::FdCanConfig, info: &'static Info, - state: &'static State, /// Reference to internals. properties: Properties, periph_clock: crate::time::Hertz, @@ -175,27 +177,30 @@ impl<'d> CanConfigurator<'d> { /// 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

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, + _peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, _irqs: impl interrupt::typelevel::Binding> + interrupt::typelevel::Binding> + 'd, ) -> CanConfigurator<'d> { - into_ref!(_peri, rx, tx); - rx.set_as_af(rx.af_num(), AfType::input(Pull::None)); tx.set_as_af(tx.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); rcc::enable_and_reset::(); + let info = T::info(); + T::info().state.lock(|s| { + s.borrow_mut().tx_pin_port = Some(tx.pin_port()); + s.borrow_mut().rx_pin_port = Some(rx.pin_port()); + }); + (info.internal_operation)(InternalOperation::NotifySenderCreated); + (info.internal_operation)(InternalOperation::NotifyReceiverCreated); + 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(Pull::None)); - tx.set_as_af(tx.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - unsafe { T::IT0Interrupt::unpend(); // Not unsafe T::IT0Interrupt::enable(); @@ -206,8 +211,7 @@ impl<'d> CanConfigurator<'d> { Self { _phantom: PhantomData, config, - info: T::info(), - state: T::state(), + info, properties: Properties::new(T::info()), periph_clock: T::frequency(), } @@ -259,19 +263,16 @@ impl<'d> CanConfigurator<'d> { /// Start in mode. pub fn start(self, mode: OperatingMode) -> Can<'d> { let ns_per_timer_tick = calc_ns_per_timer_tick(self.info, self.periph_clock, self.config.frame_transmit); - critical_section::with(|_| { - let state = self.state as *const State; - unsafe { - let mut_state = state as *mut State; - (*mut_state).ns_per_timer_tick = ns_per_timer_tick; - } + self.info.state.lock(|s| { + s.borrow_mut().ns_per_timer_tick = ns_per_timer_tick; }); self.info.regs.into_mode(self.config, mode); + (self.info.internal_operation)(InternalOperation::NotifySenderCreated); + (self.info.internal_operation)(InternalOperation::NotifyReceiverCreated); Can { _phantom: PhantomData, config: self.config, info: self.info, - state: self.state, _mode: mode, properties: Properties::new(self.info), } @@ -293,12 +294,18 @@ impl<'d> CanConfigurator<'d> { } } +impl<'d> Drop for CanConfigurator<'d> { + fn drop(&mut self) { + (self.info.internal_operation)(InternalOperation::NotifySenderDestroyed); + (self.info.internal_operation)(InternalOperation::NotifyReceiverDestroyed); + } +} + /// FDCAN Instance pub struct Can<'d> { _phantom: PhantomData<&'d ()>, config: crate::can::fd::config::FdCanConfig, info: &'static Info, - state: &'static State, _mode: OperatingMode, properties: Properties, } @@ -312,7 +319,9 @@ impl<'d> Can<'d> { /// Flush one of the TX mailboxes. pub async fn flush(&self, idx: usize) { poll_fn(|cx| { - self.state.tx_mode.register(cx.waker()); + self.info.state.lock(|s| { + s.borrow_mut().tx_mode.register(cx.waker()); + }); if idx > 3 { panic!("Bad mailbox"); @@ -332,12 +341,12 @@ impl<'d> Can<'d> { /// can be replaced, this call asynchronously waits for a frame to be successfully /// transmitted, then tries again. pub async fn write(&mut self, frame: &Frame) -> Option { - self.state.tx_mode.write(self.info, frame).await + TxMode::write(self.info, frame).await } /// Returns the next received message frame pub async fn read(&mut self) -> Result { - self.state.rx_mode.read_classic(self.info, self.state).await + RxMode::read_classic(self.info).await } /// Queues the message to be sent but exerts backpressure. If a lower-priority @@ -345,40 +354,43 @@ impl<'d> Can<'d> { /// 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 { - self.state.tx_mode.write_fd(self.info, frame).await + TxMode::write_fd(self.info, frame).await } /// Returns the next received message frame pub async fn read_fd(&mut self) -> Result { - self.state.rx_mode.read_fd(self.info, self.state).await + RxMode::read_fd(self.info).await } /// Split instance into separate portions: Tx(write), Rx(read), common properties pub fn split(self) -> (CanTx<'d>, CanRx<'d>, Properties) { + (self.info.internal_operation)(InternalOperation::NotifySenderCreated); + (self.info.internal_operation)(InternalOperation::NotifyReceiverCreated); ( CanTx { _phantom: PhantomData, info: self.info, - state: self.state, config: self.config, _mode: self._mode, }, CanRx { _phantom: PhantomData, info: self.info, - state: self.state, _mode: self._mode, }, - self.properties, + Properties { + info: self.properties.info, + }, ) } /// Join split rx and tx portions back together pub fn join(tx: CanTx<'d>, rx: CanRx<'d>) -> Self { + (tx.info.internal_operation)(InternalOperation::NotifySenderCreated); + (tx.info.internal_operation)(InternalOperation::NotifyReceiverCreated); Can { _phantom: PhantomData, config: tx.config, info: tx.info, - state: tx.state, _mode: rx._mode, properties: Properties::new(tx.info), } @@ -390,7 +402,7 @@ impl<'d> Can<'d> { tx_buf: &'static mut TxBuf, rxb: &'static mut RxBuf, ) -> BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { - BufferedCan::new(self.info, self.state, self._mode, tx_buf, rxb) + BufferedCan::new(self.info, self._mode, tx_buf, rxb) } /// Return a buffered instance of driver with CAN FD support. User must supply Buffers @@ -399,7 +411,14 @@ impl<'d> Can<'d> { tx_buf: &'static mut TxFdBuf, rxb: &'static mut RxFdBuf, ) -> BufferedCanFd<'d, TX_BUF_SIZE, RX_BUF_SIZE> { - BufferedCanFd::new(self.info, self.state, self._mode, tx_buf, rxb) + BufferedCanFd::new(self.info, self._mode, tx_buf, rxb) + } +} + +impl<'d> Drop for Can<'d> { + fn drop(&mut self) { + (self.info.internal_operation)(InternalOperation::NotifySenderDestroyed); + (self.info.internal_operation)(InternalOperation::NotifyReceiverDestroyed); } } @@ -413,7 +432,6 @@ pub type TxBuf = Channel { _phantom: PhantomData<&'d ()>, info: &'static Info, - state: &'static State, _mode: OperatingMode, tx_buf: &'static TxBuf, rx_buf: &'static RxBuf, @@ -423,15 +441,15 @@ pub struct BufferedCan<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> { impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { fn new( info: &'static Info, - state: &'static State, _mode: OperatingMode, tx_buf: &'static TxBuf, rx_buf: &'static RxBuf, ) -> Self { + (info.internal_operation)(InternalOperation::NotifySenderCreated); + (info.internal_operation)(InternalOperation::NotifyReceiverCreated); BufferedCan { _phantom: PhantomData, info, - state, _mode, tx_buf, rx_buf, @@ -447,19 +465,15 @@ impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, fn setup(self) -> Self { // We don't want interrupts being processed while we change modes. - critical_section::with(|_| { + self.info.state.lock(|s| { let rx_inner = super::common::ClassicBufferedRxInner { rx_sender: self.rx_buf.sender().into(), }; let tx_inner = super::common::ClassicBufferedTxInner { tx_receiver: self.tx_buf.receiver().into(), }; - let state = self.state as *const State; - unsafe { - let mut_state = state as *mut State; - (*mut_state).rx_mode = RxMode::ClassicBuffered(rx_inner); - (*mut_state).tx_mode = TxMode::ClassicBuffered(tx_inner); - } + s.borrow_mut().rx_mode = RxMode::ClassicBuffered(rx_inner); + s.borrow_mut().tx_mode = TxMode::ClassicBuffered(tx_inner); }); self } @@ -478,28 +492,28 @@ impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, /// Returns a sender that can be used for sending CAN frames. pub fn writer(&self) -> BufferedCanSender { + (self.info.internal_operation)(InternalOperation::NotifySenderCreated); BufferedCanSender { tx_buf: self.tx_buf.sender().into(), waker: self.info.tx_waker, + internal_operation: self.info.internal_operation, } } /// 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() + (self.info.internal_operation)(InternalOperation::NotifyReceiverCreated); + BufferedCanReceiver { + rx_buf: self.rx_buf.receiver().into(), + internal_operation: self.info.internal_operation, + } } } impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> Drop for BufferedCan<'d, TX_BUF_SIZE, RX_BUF_SIZE> { fn drop(&mut self) { - critical_section::with(|_| { - let state = self.state as *const State; - unsafe { - let mut_state = state as *mut State; - (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); - (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); - } - }); + (self.info.internal_operation)(InternalOperation::NotifySenderDestroyed); + (self.info.internal_operation)(InternalOperation::NotifyReceiverDestroyed); } } @@ -509,41 +523,16 @@ pub type RxFdBuf = Channel = Channel; -/// Sender that can be used for sending CAN frames. -#[derive(Copy, Clone)] -pub struct BufferedFdCanSender { - tx_buf: 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> { - 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) - } -} +/// Sender that can be used for sending Classic CAN frames. +pub type BufferedFdCanSender = super::common::BufferedSender<'static, FdFrame>; /// Receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. -pub type BufferedFdCanReceiver = DynamicReceiver<'static, Result>; +pub type BufferedFdCanReceiver = super::common::BufferedReceiver<'static, FdEnvelope>; /// Buffered FDCAN Instance pub struct BufferedCanFd<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> { _phantom: PhantomData<&'d ()>, info: &'static Info, - state: &'static State, _mode: OperatingMode, tx_buf: &'static TxFdBuf, rx_buf: &'static RxFdBuf, @@ -553,15 +542,15 @@ pub struct BufferedCanFd<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCanFd<'d, TX_BUF_SIZE, RX_BUF_SIZE> { fn new( info: &'static Info, - state: &'static State, _mode: OperatingMode, tx_buf: &'static TxFdBuf, rx_buf: &'static RxFdBuf, ) -> Self { + (info.internal_operation)(InternalOperation::NotifySenderCreated); + (info.internal_operation)(InternalOperation::NotifyReceiverCreated); BufferedCanFd { _phantom: PhantomData, info, - state, _mode, tx_buf, rx_buf, @@ -577,19 +566,15 @@ impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCanFd<' fn setup(self) -> Self { // We don't want interrupts being processed while we change modes. - critical_section::with(|_| { + self.info.state.lock(|s| { let rx_inner = super::common::FdBufferedRxInner { rx_sender: self.rx_buf.sender().into(), }; let tx_inner = super::common::FdBufferedTxInner { tx_receiver: self.tx_buf.receiver().into(), }; - let state = self.state as *const State; - unsafe { - let mut_state = state as *mut State; - (*mut_state).rx_mode = RxMode::FdBuffered(rx_inner); - (*mut_state).tx_mode = TxMode::FdBuffered(tx_inner); - } + s.borrow_mut().rx_mode = RxMode::FdBuffered(rx_inner); + s.borrow_mut().tx_mode = TxMode::FdBuffered(tx_inner); }); self } @@ -608,28 +593,28 @@ impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCanFd<' /// Returns a sender that can be used for sending CAN frames. pub fn writer(&self) -> BufferedFdCanSender { + (self.info.internal_operation)(InternalOperation::NotifySenderCreated); BufferedFdCanSender { tx_buf: self.tx_buf.sender().into(), waker: self.info.tx_waker, + internal_operation: self.info.internal_operation, } } /// 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() + (self.info.internal_operation)(InternalOperation::NotifyReceiverCreated); + BufferedFdCanReceiver { + rx_buf: self.rx_buf.receiver().into(), + internal_operation: self.info.internal_operation, + } } } impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> Drop for BufferedCanFd<'d, TX_BUF_SIZE, RX_BUF_SIZE> { fn drop(&mut self) { - critical_section::with(|_| { - let state = self.state as *const State; - unsafe { - let mut_state = state as *mut State; - (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); - (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); - } - }); + (self.info.internal_operation)(InternalOperation::NotifySenderDestroyed); + (self.info.internal_operation)(InternalOperation::NotifyReceiverDestroyed); } } @@ -637,19 +622,24 @@ impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> Drop for Buffer pub struct CanRx<'d> { _phantom: PhantomData<&'d ()>, info: &'static Info, - state: &'static State, _mode: OperatingMode, } impl<'d> CanRx<'d> { /// Returns the next received message frame pub async fn read(&mut self) -> Result { - self.state.rx_mode.read_classic(&self.info, &self.state).await + RxMode::read_classic(&self.info).await } /// Returns the next received message frame pub async fn read_fd(&mut self) -> Result { - self.state.rx_mode.read_fd(&self.info, &self.state).await + RxMode::read_fd(&self.info).await + } +} + +impl<'d> Drop for CanRx<'d> { + fn drop(&mut self) { + (self.info.internal_operation)(InternalOperation::NotifyReceiverDestroyed); } } @@ -657,7 +647,6 @@ impl<'d> CanRx<'d> { pub struct CanTx<'d> { _phantom: PhantomData<&'d ()>, info: &'static Info, - state: &'static State, config: crate::can::fd::config::FdCanConfig, _mode: OperatingMode, } @@ -668,7 +657,7 @@ impl<'c, 'd> CanTx<'d> { /// can be replaced, this call asynchronously waits for a frame to be successfully /// transmitted, then tries again. pub async fn write(&mut self, frame: &Frame) -> Option { - self.state.tx_mode.write(self.info, frame).await + TxMode::write(self.info, frame).await } /// Queues the message to be sent but exerts backpressure. If a lower-priority @@ -676,7 +665,13 @@ impl<'c, 'd> CanTx<'d> { /// 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 { - self.state.tx_mode.write_fd(self.info, frame).await + TxMode::write_fd(self.info, frame).await + } +} + +impl<'d> Drop for CanTx<'d> { + fn drop(&mut self) { + (self.info.internal_operation)(InternalOperation::NotifySenderDestroyed); } } @@ -696,19 +691,19 @@ impl RxMode { } } - fn on_interrupt(&self, fifonr: usize) { + fn on_interrupt(&self, fifonr: usize, ns_per_timer_tick: u64) { T::registers().regs.ir().write(|w| w.set_rfn(fifonr, true)); match self { RxMode::NonBuffered(waker) => { waker.wake(); } RxMode::ClassicBuffered(buf) => { - if let Some(result) = self.try_read::() { + if let Some(result) = self.try_read::(ns_per_timer_tick) { let _ = buf.rx_sender.try_send(result); } } RxMode::FdBuffered(buf) => { - if let Some(result) = self.try_read_fd::() { + if let Some(result) = self.try_read_fd::(ns_per_timer_tick) { let _ = buf.rx_sender.try_send(result); } } @@ -716,12 +711,12 @@ impl RxMode { } //async fn read_classic(&self) -> Result { - fn try_read(&self) -> Option> { + fn try_read(&self, ns_per_timer_tick: u64) -> Option> { if let Some((frame, ts)) = T::registers().read(0) { - let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + let ts = T::registers().calc_timestamp(ns_per_timer_tick, ts); Some(Ok(Envelope { ts, frame })) } else if let Some((frame, ts)) = T::registers().read(1) { - let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + let ts = T::registers().calc_timestamp(ns_per_timer_tick, ts); Some(Ok(Envelope { ts, frame })) } else if let Some(err) = T::registers().curr_error() { // TODO: this is probably wrong @@ -731,12 +726,12 @@ impl RxMode { } } - fn try_read_fd(&self) -> Option> { + fn try_read_fd(&self, ns_per_timer_tick: u64) -> Option> { if let Some((frame, ts)) = T::registers().read(0) { - let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + let ts = T::registers().calc_timestamp(ns_per_timer_tick, ts); Some(Ok(FdEnvelope { ts, frame })) } else if let Some((frame, ts)) = T::registers().read(1) { - let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + let ts = T::registers().calc_timestamp(ns_per_timer_tick, ts); Some(Ok(FdEnvelope { ts, frame })) } else if let Some(err) = T::registers().curr_error() { // TODO: this is probably wrong @@ -746,16 +741,12 @@ impl RxMode { } } - fn read( - &self, - info: &'static Info, - state: &'static State, - ) -> Option> { + fn read(info: &'static Info, ns_per_timer_tick: u64) -> Option> { if let Some((msg, ts)) = info.regs.read(0) { - let ts = info.calc_timestamp(state.ns_per_timer_tick, ts); + let ts = info.regs.calc_timestamp(ns_per_timer_tick, ts); Some(Ok((msg, ts))) } else if let Some((msg, ts)) = info.regs.read(1) { - let ts = info.calc_timestamp(state.ns_per_timer_tick, ts); + let ts = info.regs.calc_timestamp(ns_per_timer_tick, ts); Some(Ok((msg, ts))) } else if let Some(err) = info.regs.curr_error() { // TODO: this is probably wrong @@ -765,16 +756,15 @@ impl RxMode { } } - async fn read_async( - &self, - info: &'static Info, - state: &'static State, - ) -> Result<(F, Timestamp), BusError> { - //let _ = self.read::(info, state); + async fn read_async(info: &'static Info) -> Result<(F, Timestamp), BusError> { poll_fn(move |cx| { - state.err_waker.register(cx.waker()); - self.register(cx.waker()); - match self.read::<_>(info, state) { + let ns_per_timer_tick = info.state.lock(|s| { + let state = s.borrow_mut(); + state.err_waker.register(cx.waker()); + state.rx_mode.register(cx.waker()); + state.ns_per_timer_tick + }); + match RxMode::read::<_>(info, ns_per_timer_tick) { Some(result) => Poll::Ready(result), None => Poll::Pending, } @@ -782,15 +772,15 @@ impl RxMode { .await } - async fn read_classic(&self, info: &'static Info, state: &'static State) -> Result { - match self.read_async::<_>(info, state).await { + async fn read_classic(info: &'static Info) -> Result { + match RxMode::read_async::<_>(info).await { Ok((frame, ts)) => Ok(Envelope { ts, frame }), Err(e) => Err(e), } } - async fn read_fd(&self, info: &'static Info, state: &'static State) -> Result { - match self.read_async::<_>(info, state).await { + async fn read_fd(info: &'static Info) -> Result { + match RxMode::read_async::<_>(info).await { Ok((frame, ts)) => Ok(FdEnvelope { ts, frame }), Err(e) => Err(e), } @@ -819,9 +809,11 @@ impl TxMode { /// 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(&self, info: &'static Info, frame: &F) -> Option { + async fn write_generic(info: &'static Info, frame: &F) -> Option { poll_fn(|cx| { - self.register(cx.waker()); + info.state.lock(|s| { + s.borrow_mut().tx_mode.register(cx.waker()); + }); if let Ok(dropped) = info.regs.write(frame) { return Poll::Ready(dropped); @@ -838,16 +830,16 @@ impl TxMode { /// 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(&self, info: &'static Info, frame: &Frame) -> Option { - self.write_generic::<_>(info, frame).await + async fn write(info: &'static Info, frame: &Frame) -> Option { + TxMode::write_generic::<_>(info, 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. - async fn write_fd(&self, info: &'static Info, frame: &FdFrame) -> Option { - self.write_generic::<_>(info, frame).await + async fn write_fd(info: &'static Info, frame: &FdFrame) -> Option { + TxMode::write_generic::<_>(info, frame).await } } @@ -922,6 +914,10 @@ struct State { pub rx_mode: RxMode, pub tx_mode: TxMode, pub ns_per_timer_tick: u64, + receiver_instance_count: usize, + sender_instance_count: usize, + tx_pin_port: Option, + rx_pin_port: Option, pub err_waker: AtomicWaker, } @@ -933,34 +929,22 @@ impl State { tx_mode: TxMode::NonBuffered(AtomicWaker::new()), ns_per_timer_tick: 0, err_waker: AtomicWaker::new(), + receiver_instance_count: 0, + sender_instance_count: 0, + tx_pin_port: None, + rx_pin_port: None, } } } +type SharedState = embassy_sync::blocking_mutex::Mutex>; struct Info { regs: Registers, interrupt0: crate::interrupt::Interrupt, _interrupt1: crate::interrupt::Interrupt, tx_waker: fn(), -} - -impl Info { - #[cfg(feature = "time")] - fn calc_timestamp(&self, 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.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(&self, _ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { - ts_val - } + internal_operation: fn(InternalOperation), + state: SharedState, } trait SealedInstance { @@ -968,14 +952,12 @@ trait SealedInstance { fn info() -> &'static Info; fn registers() -> crate::can::fd::peripheral::Registers; - fn state() -> &'static State; - unsafe fn mut_state() -> &'static mut State; - fn calc_timestamp(ns_per_timer_tick: u64, ts_val: u16) -> Timestamp; + fn internal_operation(val: InternalOperation); } /// Instance trait #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + 'static { +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static { /// Interrupt 0 type IT0Interrupt: crate::interrupt::typelevel::Interrupt; /// Interrupt 1 @@ -983,7 +965,7 @@ pub trait Instance: SealedInstance + RccPeripheral + 'static { } /// Fdcan Instance struct -pub struct FdcanInstance<'a, T>(PeripheralRef<'a, T>); +pub struct FdcanInstance<'a, T: Instance>(Peri<'a, T>); macro_rules! impl_fdcan { ($inst:ident, @@ -992,42 +974,56 @@ macro_rules! impl_fdcan { impl SealedInstance for peripherals::$inst { const MSG_RAM_OFFSET: usize = $msg_ram_offset; + fn internal_operation(val: InternalOperation) { + peripherals::$inst::info().state.lock(|s| { + let mut mut_state = s.borrow_mut(); + match val { + InternalOperation::NotifySenderCreated => { + mut_state.sender_instance_count += 1; + } + InternalOperation::NotifySenderDestroyed => { + mut_state.sender_instance_count -= 1; + if ( 0 == mut_state.sender_instance_count) { + (*mut_state).tx_mode = TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + } + InternalOperation::NotifyReceiverCreated => { + mut_state.receiver_instance_count += 1; + } + InternalOperation::NotifyReceiverDestroyed => { + mut_state.receiver_instance_count -= 1; + if ( 0 == mut_state.receiver_instance_count) { + (*mut_state).rx_mode = RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + } + } + } + if mut_state.sender_instance_count == 0 && mut_state.receiver_instance_count == 0 { + unsafe { + let tx_pin = crate::gpio::AnyPin::steal(mut_state.tx_pin_port.unwrap()); + tx_pin.set_as_disconnected(); + let rx_pin = crate::gpio::AnyPin::steal(mut_state.rx_pin_port.unwrap()); + rx_pin.set_as_disconnected(); + rcc::disable::(); + } + } + }); + } + fn info() -> &'static Info { + static INFO: Info = Info { regs: Registers{regs: crate::pac::$inst, msgram: crate::pac::$msg_ram_inst, msg_ram_offset: $msg_ram_offset}, interrupt0: crate::_generated::peripheral_interrupts::$inst::IT0::IRQ, _interrupt1: crate::_generated::peripheral_interrupts::$inst::IT1::IRQ, tx_waker: crate::_generated::peripheral_interrupts::$inst::IT0::pend, + internal_operation: peripherals::$inst::internal_operation, + state: embassy_sync::blocking_mutex::Mutex::new(core::cell::RefCell::new(State::new())), }; &INFO } fn registers() -> Registers { Registers{regs: crate::pac::$inst, msgram: crate::pac::$msg_ram_inst, msg_ram_offset: Self::MSG_RAM_OFFSET} } - unsafe fn mut_state() -> &'static mut State { - static mut STATE: State = State::new(); - &mut *core::ptr::addr_of_mut!(STATE) - } - fn state() -> &'static 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::registers().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 - } } diff --git a/embassy-stm32/src/can/frame.rs b/embassy-stm32/src/can/frame.rs index d2d1f7aa6..0fbab053b 100644 --- a/embassy-stm32/src/can/frame.rs +++ b/embassy-stm32/src/can/frame.rs @@ -104,15 +104,13 @@ pub trait CanHeader: Sized { #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ClassicData { - pub(crate) bytes: [u8; Self::MAX_DATA_LEN], + pub(crate) bytes: [u8; 8], } impl ClassicData { - pub(crate) const MAX_DATA_LEN: usize = 8; /// 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. + /// Returns `FrameCreateError` if `data` is more than 8 bytes (which is the maximum). pub fn new(data: &[u8]) -> Result { if data.len() > 8 { return Err(FrameCreateError::InvalidDataLength); @@ -129,6 +127,11 @@ impl ClassicData { &self.bytes } + /// Raw mutable read access to data. + pub fn raw_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } + /// Checks if the length can be encoded in FDCAN DLC field. pub const fn is_valid_len(len: usize) -> bool { match len { @@ -206,7 +209,17 @@ impl Frame { /// Get reference to data pub fn data(&self) -> &[u8] { - &self.data.raw() + &self.data.raw()[..self.can_header.len as usize] + } + + /// Get reference to underlying 8-byte raw data buffer, some bytes on the tail might be undefined. + pub fn raw_data(&self) -> &[u8] { + self.data.raw() + } + + /// Get mutable reference to data + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.data.raw_mut()[..self.can_header.len as usize] } /// Get priority of frame @@ -250,7 +263,7 @@ impl embedded_can::Frame for Frame { self.can_header.len as usize } fn data(&self) -> &[u8] { - &self.data.raw() + &self.data() } } @@ -314,6 +327,11 @@ impl FdData { &self.bytes } + /// Raw mutable read access to data. + pub fn raw_mut(&mut self) -> &mut [u8] { + &mut self.bytes + } + /// Checks if the length can be encoded in FDCAN DLC field. pub const fn is_valid_len(len: usize) -> bool { match len { @@ -390,7 +408,12 @@ impl FdFrame { /// Get reference to data pub fn data(&self) -> &[u8] { - &self.data.raw() + &self.data.raw()[..self.can_header.len as usize] + } + + /// Get mutable reference to data + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.data.raw_mut()[..self.can_header.len as usize] } } @@ -428,7 +451,7 @@ impl embedded_can::Frame for FdFrame { self.can_header.len as usize } fn data(&self) -> &[u8] { - &self.data.raw() + &self.data() } } diff --git a/embassy-stm32/src/cordic/mod.rs b/embassy-stm32/src/cordic/mod.rs index fb342d2e7..320774857 100644 --- a/embassy-stm32/src/cordic/mod.rs +++ b/embassy-stm32/src/cordic/mod.rs @@ -1,7 +1,7 @@ //! coordinate rotation digital computer (CORDIC) use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use crate::pac::cordic::vals; use crate::{dma, peripherals, rcc}; @@ -16,7 +16,7 @@ pub mod utils; /// CORDIC driver pub struct Cordic<'d, T: Instance> { - peri: PeripheralRef<'d, T>, + peri: Peri<'d, T>, config: Config, } @@ -137,7 +137,7 @@ trait SealedInstance { /// CORDIC instance trait #[allow(private_bounds)] -pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral {} +pub trait Instance: SealedInstance + PeripheralType + crate::rcc::RccPeripheral {} /// CORDIC configuration #[derive(Debug)] @@ -198,11 +198,9 @@ impl<'d, T: Instance> Cordic<'d, T> { /// Note: /// If you need a peripheral -> CORDIC -> peripheral mode, /// you may want to set Cordic into [Mode::ZeroOverhead] mode, and add extra arguments with [Self::extra_config] - pub fn new(peri: impl Peripheral

+ 'd, config: Config) -> Self { + pub fn new(peri: Peri<'d, T>, config: Config) -> Self { rcc::enable_and_reset::(); - into_ref!(peri); - let mut instance = Self { peri, config }; instance.reconfigure(); @@ -378,8 +376,8 @@ impl<'d, T: Instance> Cordic<'d, T> { /// If you want to make sure ARG2 is set to +1, consider run [.reconfigure()](Self::reconfigure). pub async fn async_calc_32bit( &mut self, - write_dma: impl Peripheral

>, - read_dma: impl Peripheral

>, + mut write_dma: Peri<'_, impl WriteDma>, + mut read_dma: Peri<'_, impl ReadDma>, arg: &[u32], res: &mut [u32], arg1_only: bool, @@ -393,8 +391,6 @@ impl<'d, T: Instance> Cordic<'d, T> { let active_res_buf = &mut res[..res_cnt]; - into_ref!(write_dma, read_dma); - self.peri .set_argument_count(if arg1_only { AccessCount::One } else { AccessCount::Two }); @@ -416,7 +412,7 @@ impl<'d, T: Instance> Cordic<'d, T> { unsafe { let write_transfer = dma::Transfer::new_write( - &mut write_dma, + write_dma.reborrow(), write_req, arg, T::regs().wdata().as_ptr() as *mut _, @@ -424,7 +420,7 @@ impl<'d, T: Instance> Cordic<'d, T> { ); let read_transfer = dma::Transfer::new_read( - &mut read_dma, + read_dma.reborrow(), read_req, T::regs().rdata().as_ptr() as *mut _, active_res_buf, @@ -519,8 +515,8 @@ impl<'d, T: Instance> Cordic<'d, T> { /// User will take respond to merge two u16 arguments into one u32 data, and/or split one u32 data into two u16 results. pub async fn async_calc_16bit( &mut self, - write_dma: impl Peripheral

>, - read_dma: impl Peripheral

>, + mut write_dma: Peri<'_, impl WriteDma>, + mut read_dma: Peri<'_, impl ReadDma>, arg: &[u32], res: &mut [u32], ) -> Result { @@ -536,8 +532,6 @@ impl<'d, T: Instance> Cordic<'d, T> { let active_res_buf = &mut res[..res_cnt]; - into_ref!(write_dma, read_dma); - // In q1.15 mode, 1 write/read to access 2 arguments/results self.peri.set_argument_count(AccessCount::One); self.peri.set_result_count(AccessCount::One); @@ -557,7 +551,7 @@ impl<'d, T: Instance> Cordic<'d, T> { unsafe { let write_transfer = dma::Transfer::new_write( - &mut write_dma, + write_dma.reborrow(), write_req, arg, T::regs().wdata().as_ptr() as *mut _, @@ -565,7 +559,7 @@ impl<'d, T: Instance> Cordic<'d, T> { ); let read_transfer = dma::Transfer::new_read( - &mut read_dma, + read_dma.reborrow(), read_req, T::regs().rdata().as_ptr() as *mut _, active_res_buf, diff --git a/embassy-stm32/src/crc/v1.rs b/embassy-stm32/src/crc/v1.rs index f3d13de7c..13e5263de 100644 --- a/embassy-stm32/src/crc/v1.rs +++ b/embassy-stm32/src/crc/v1.rs @@ -1,23 +1,18 @@ -use embassy_hal_internal::{into_ref, PeripheralRef}; - use crate::pac::CRC as PAC_CRC; use crate::peripherals::CRC; -use crate::{rcc, Peripheral}; +use crate::{rcc, Peri}; /// CRC driver. pub struct Crc<'d> { - _peri: PeripheralRef<'d, CRC>, + _peri: Peri<'d, CRC>, } impl<'d> Crc<'d> { /// Instantiates the CRC32 peripheral and initializes it to default values. - pub fn new(peripheral: impl Peripheral

+ 'd) -> Self { - into_ref!(peripheral); - + pub fn new(peripheral: Peri<'d, CRC>) -> Self { // Note: enable and reset come from RccPeripheral. // enable CRC clock in RCC. rcc::enable_and_reset::(); - // Peripheral the peripheral let mut instance = Self { _peri: peripheral }; instance.reset(); instance @@ -28,22 +23,16 @@ impl<'d> Crc<'d> { PAC_CRC.cr().write(|w| w.set_reset(true)); } - /// Feeds a word to the peripheral and returns the current CRC value + /// Feeds a word into the CRC peripheral. Returns the computed CRC. pub fn feed_word(&mut self, word: u32) -> u32 { // write a single byte to the device, and return the result - #[cfg(not(crc_v1))] - PAC_CRC.dr32().write_value(word); - #[cfg(crc_v1)] PAC_CRC.dr().write_value(word); self.read() } - /// Feed a slice of words to the peripheral and return the result. + /// Feeds a slice of words into the CRC peripheral. Returns the computed CRC. pub fn feed_words(&mut self, words: &[u32]) -> u32 { for word in words { - #[cfg(not(crc_v1))] - PAC_CRC.dr32().write_value(*word); - #[cfg(crc_v1)] PAC_CRC.dr().write_value(*word); } @@ -51,12 +40,6 @@ impl<'d> Crc<'d> { } /// Read the CRC result value. - #[cfg(not(crc_v1))] - pub fn read(&self) -> u32 { - PAC_CRC.dr32().read() - } - /// Read the CRC result value. - #[cfg(crc_v1)] pub fn read(&self) -> u32 { PAC_CRC.dr().read() } diff --git a/embassy-stm32/src/crc/v2v3.rs b/embassy-stm32/src/crc/v2v3.rs index 09d956d7c..d834d0971 100644 --- a/embassy-stm32/src/crc/v2v3.rs +++ b/embassy-stm32/src/crc/v2v3.rs @@ -1,17 +1,15 @@ -use embassy_hal_internal::{into_ref, PeripheralRef}; - use crate::pac::crc::vals; use crate::pac::CRC as PAC_CRC; use crate::peripherals::CRC; -use crate::{rcc, Peripheral}; +use crate::{rcc, Peri}; /// CRC driver. pub struct Crc<'d> { - _peripheral: PeripheralRef<'d, CRC>, + _peripheral: Peri<'d, CRC>, _config: Config, } -/// CRC configuration errlr +/// CRC configuration error #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ConfigError { @@ -36,9 +34,9 @@ pub enum InputReverseConfig { None, /// Reverse bytes Byte, - /// Reverse 16-bit halfwords. + /// Reverse 16-bit halfwords Halfword, - /// Reverse 32-bit words. + /// Reverse 32-bit words Word, } @@ -80,11 +78,10 @@ pub enum PolySize { impl<'d> Crc<'d> { /// Instantiates the CRC32 peripheral and initializes it to default values. - pub fn new(peripheral: impl Peripheral

+ 'd, config: Config) -> Self { + pub fn new(peripheral: Peri<'d, CRC>, config: Config) -> Self { // Note: enable and reset come from RccPeripheral. // reset to default values and enable CRC clock in RCC. rcc::enable_and_reset::(); - into_ref!(peripheral); let mut instance = Self { _peripheral: peripheral, _config: config, @@ -118,7 +115,7 @@ impl<'d> Crc<'d> { w.set_rev_in(match self._config.reverse_in { InputReverseConfig::None => vals::RevIn::NORMAL, InputReverseConfig::Byte => vals::RevIn::BYTE, - InputReverseConfig::Halfword => vals::RevIn::HALFWORD, + InputReverseConfig::Halfword => vals::RevIn::HALF_WORD, InputReverseConfig::Word => vals::RevIn::WORD, }); // configure the polynomial. @@ -130,45 +127,52 @@ impl<'d> Crc<'d> { PolySize::Width32 => vals::Polysize::POLYSIZE32, }); }); - - self.reset(); } - /// Feeds a byte into the CRC peripheral. Returns the computed checksum. - pub fn feed_byte(&mut self, byte: u8) -> u32 { - PAC_CRC.dr8().write_value(byte); + /// Read the CRC result value. + pub fn read(&self) -> u32 { PAC_CRC.dr32().read() } - /// Feeds an slice of bytes into the CRC peripheral. Returns the computed checksum. + /// Feeds a byte into the CRC peripheral. Returns the computed CRC. + pub fn feed_byte(&mut self, byte: u8) -> u32 { + PAC_CRC.dr8().write_value(byte); + self.read() + } + + /// Feeds a slice of bytes into the CRC peripheral. Returns the computed CRC. pub fn feed_bytes(&mut self, bytes: &[u8]) -> u32 { for byte in bytes { PAC_CRC.dr8().write_value(*byte); } - PAC_CRC.dr32().read() + self.read() } - /// Feeds a halfword into the CRC peripheral. Returns the computed checksum. + + /// Feeds a halfword into the CRC peripheral. Returns the computed CRC. pub fn feed_halfword(&mut self, halfword: u16) -> u32 { PAC_CRC.dr16().write_value(halfword); - PAC_CRC.dr32().read() + self.read() } - /// Feeds an slice of halfwords into the CRC peripheral. Returns the computed checksum. + + /// Feeds a slice of halfwords into the CRC peripheral. Returns the computed CRC. pub fn feed_halfwords(&mut self, halfwords: &[u16]) -> u32 { for halfword in halfwords { PAC_CRC.dr16().write_value(*halfword); } - PAC_CRC.dr32().read() + self.read() } - /// Feeds a words into the CRC peripheral. Returns the computed checksum. + + /// Feeds a word into the CRC peripheral. Returns the computed CRC. pub fn feed_word(&mut self, word: u32) -> u32 { PAC_CRC.dr32().write_value(word as u32); - PAC_CRC.dr32().read() + self.read() } - /// Feeds an slice of words into the CRC peripheral. Returns the computed checksum. + + /// Feeds a slice of words into the CRC peripheral. Returns the computed CRC. pub fn feed_words(&mut self, words: &[u32]) -> u32 { for word in words { PAC_CRC.dr32().write_value(*word as u32); } - PAC_CRC.dr32().read() + self.read() } } diff --git a/embassy-stm32/src/cryp/mod.rs b/embassy-stm32/src/cryp/mod.rs index 8d600c73c..fba3c0fd7 100644 --- a/embassy-stm32/src/cryp/mod.rs +++ b/embassy-stm32/src/cryp/mod.rs @@ -4,12 +4,13 @@ use core::cmp::min; use core::marker::PhantomData; use core::ptr; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; -use crate::dma::{NoDma, Transfer, TransferOptions}; +use crate::dma::{ChannelAndRequest, TransferOptions}; use crate::interrupt::typelevel::Interrupt; -use crate::{interrupt, pac, peripherals, rcc, Peripheral}; +use crate::mode::{Async, Blocking, Mode}; +use crate::{interrupt, pac, peripherals, rcc}; const DES_BLOCK_SIZE: usize = 8; // 64 bits const AES_BLOCK_SIZE: usize = 16; // 128 bits @@ -57,15 +58,10 @@ pub trait Cipher<'c> { fn prepare_key(&self, _p: pac::cryp::Cryp) {} /// Performs any cipher-specific initialization. - fn init_phase_blocking(&self, _p: pac::cryp::Cryp, _cryp: &Cryp) {} + fn init_phase_blocking(&self, _p: pac::cryp::Cryp, _cryp: &Cryp) {} /// Performs any cipher-specific initialization. - async fn init_phase(&self, _p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) - where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { - } + async fn init_phase(&self, _p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, Async>) {} /// Called prior to processing the last data block for cipher-specific operations. fn pre_final(&self, _p: pac::cryp::Cryp, _dir: Direction, _padding_len: usize) -> [u32; 4] { @@ -73,10 +69,10 @@ pub trait Cipher<'c> { } /// Called after processing the last data block for cipher-specific operations. - fn post_final_blocking( + fn post_final_blocking( &self, _p: pac::cryp::Cryp, - _cryp: &Cryp, + _cryp: &Cryp, _dir: Direction, _int_data: &mut [u8; AES_BLOCK_SIZE], _temp1: [u32; 4], @@ -85,18 +81,15 @@ pub trait Cipher<'c> { } /// Called after processing the last data block for cipher-specific operations. - async fn post_final( + async fn post_final( &self, _p: pac::cryp::Cryp, - _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + _cryp: &mut Cryp<'_, T, Async>, _dir: Direction, _int_data: &mut [u8; AES_BLOCK_SIZE], _temp1: [u32; 4], _padding_mask: [u8; 16], - ) where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { + ) { } /// Returns the AAD header block as required by the cipher. @@ -474,13 +467,13 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGcm<'c, KEY_SIZE> { p.cr().modify(|w| w.set_algomode3(true)); } - fn init_phase_blocking(&self, p: pac::cryp::Cryp, _cryp: &Cryp) { + fn init_phase_blocking(&self, p: pac::cryp::Cryp, _cryp: &Cryp) { p.cr().modify(|w| w.set_gcm_ccmph(0)); p.cr().modify(|w| w.set_crypen(true)); while p.cr().read().crypen() {} } - async fn init_phase(&self, p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) { + async fn init_phase(&self, p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, Async>) { p.cr().modify(|w| w.set_gcm_ccmph(0)); p.cr().modify(|w| w.set_crypen(true)); while p.cr().read().crypen() {} @@ -508,10 +501,10 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGcm<'c, KEY_SIZE> { } #[cfg(cryp_v2)] - fn post_final_blocking( + fn post_final_blocking( &self, p: pac::cryp::Cryp, - cryp: &Cryp, + cryp: &Cryp, dir: Direction, int_data: &mut [u8; AES_BLOCK_SIZE], _temp1: [u32; 4], @@ -534,18 +527,15 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGcm<'c, KEY_SIZE> { } #[cfg(cryp_v2)] - async fn post_final( + async fn post_final( &self, p: pac::cryp::Cryp, - cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + cryp: &mut Cryp<'_, T, Async>, dir: Direction, int_data: &mut [u8; AES_BLOCK_SIZE], _temp1: [u32; 4], padding_mask: [u8; AES_BLOCK_SIZE], - ) where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { + ) { if dir == Direction::Encrypt { // Handle special GCM partial block process. p.cr().modify(|w| w.set_crypen(false)); @@ -559,8 +549,8 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGcm<'c, KEY_SIZE> { let mut out_data: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; - let read = Cryp::::read_bytes(&mut cryp.outdma, Self::BLOCK_SIZE, &mut out_data); - let write = Cryp::::write_bytes(&mut cryp.indma, Self::BLOCK_SIZE, int_data); + let read = Cryp::::read_bytes(cryp.outdma.as_mut().unwrap(), Self::BLOCK_SIZE, &mut out_data); + let write = Cryp::::write_bytes(cryp.indma.as_mut().unwrap(), Self::BLOCK_SIZE, int_data); embassy_futures::join::join(read, write).await; @@ -615,13 +605,13 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGmac<'c, KEY_SIZE> { p.cr().modify(|w| w.set_algomode3(true)); } - fn init_phase_blocking(&self, p: pac::cryp::Cryp, _cryp: &Cryp) { + fn init_phase_blocking(&self, p: pac::cryp::Cryp, _cryp: &Cryp) { p.cr().modify(|w| w.set_gcm_ccmph(0)); p.cr().modify(|w| w.set_crypen(true)); while p.cr().read().crypen() {} } - async fn init_phase(&self, p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) { + async fn init_phase(&self, p: pac::cryp::Cryp, _cryp: &mut Cryp<'_, T, Async>) { p.cr().modify(|w| w.set_gcm_ccmph(0)); p.cr().modify(|w| w.set_crypen(true)); while p.cr().read().crypen() {} @@ -649,10 +639,10 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGmac<'c, KEY_SIZE> { } #[cfg(cryp_v2)] - fn post_final_blocking( + fn post_final_blocking( &self, p: pac::cryp::Cryp, - cryp: &Cryp, + cryp: &Cryp, dir: Direction, int_data: &mut [u8; AES_BLOCK_SIZE], _temp1: [u32; 4], @@ -675,18 +665,15 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGmac<'c, KEY_SIZE> { } #[cfg(cryp_v2)] - async fn post_final( + async fn post_final( &self, p: pac::cryp::Cryp, - cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + cryp: &mut Cryp<'_, T, Async>, dir: Direction, int_data: &mut [u8; AES_BLOCK_SIZE], _temp1: [u32; 4], padding_mask: [u8; AES_BLOCK_SIZE], - ) where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { + ) { if dir == Direction::Encrypt { // Handle special GCM partial block process. p.cr().modify(|w| w.set_crypen(false)); @@ -700,8 +687,8 @@ impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGmac<'c, KEY_SIZE> { let mut out_data: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; - let read = Cryp::::read_bytes(&mut cryp.outdma, Self::BLOCK_SIZE, &mut out_data); - let write = Cryp::::write_bytes(&mut cryp.indma, Self::BLOCK_SIZE, int_data); + let read = Cryp::::read_bytes(cryp.outdma.as_mut().unwrap(), Self::BLOCK_SIZE, &mut out_data); + let write = Cryp::::write_bytes(cryp.indma.as_mut().unwrap(), Self::BLOCK_SIZE, int_data); embassy_futures::join::join(read, write).await; } @@ -812,7 +799,7 @@ impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> Cip p.cr().modify(|w| w.set_algomode3(true)); } - fn init_phase_blocking(&self, p: pac::cryp::Cryp, cryp: &Cryp) { + fn init_phase_blocking(&self, p: pac::cryp::Cryp, cryp: &Cryp) { p.cr().modify(|w| w.set_gcm_ccmph(0)); cryp.write_bytes_blocking(Self::BLOCK_SIZE, &self.block0); @@ -821,14 +808,10 @@ impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> Cip while p.cr().read().crypen() {} } - async fn init_phase(&self, p: pac::cryp::Cryp, cryp: &mut Cryp<'_, T, DmaIn, DmaOut>) - where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { + async fn init_phase(&self, p: pac::cryp::Cryp, cryp: &mut Cryp<'_, T, Async>) { p.cr().modify(|w| w.set_gcm_ccmph(0)); - Cryp::::write_bytes(&mut cryp.indma, Self::BLOCK_SIZE, &self.block0).await; + Cryp::::write_bytes(cryp.indma.as_mut().unwrap(), Self::BLOCK_SIZE, &self.block0).await; p.cr().modify(|w| w.set_crypen(true)); while p.cr().read().crypen() {} @@ -865,10 +848,10 @@ impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> Cip } #[cfg(cryp_v2)] - fn post_final_blocking( + fn post_final_blocking( &self, p: pac::cryp::Cryp, - cryp: &Cryp, + cryp: &Cryp, dir: Direction, int_data: &mut [u8; AES_BLOCK_SIZE], temp1: [u32; 4], @@ -902,18 +885,15 @@ impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> Cip } #[cfg(cryp_v2)] - async fn post_final( + async fn post_final( &self, p: pac::cryp::Cryp, - cryp: &mut Cryp<'_, T, DmaIn, DmaOut>, + cryp: &mut Cryp<'_, T, Async>, dir: Direction, int_data: &mut [u8; AES_BLOCK_SIZE], temp1: [u32; 4], padding_mask: [u8; 16], - ) where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { + ) { if dir == Direction::Decrypt { //Handle special CCM partial block process. let mut temp2 = [0; 4]; @@ -937,7 +917,7 @@ impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> Cip in_data[i] = int_word; in_data[i] = in_data[i] ^ temp1[i] ^ temp2[i]; } - Cryp::::write_words(&mut cryp.indma, Self::BLOCK_SIZE, &in_data).await; + Cryp::::write_words(cryp.indma.as_mut().unwrap(), Self::BLOCK_SIZE, &in_data).await; } } } @@ -1007,26 +987,25 @@ pub enum Direction { } /// Crypto Accelerator Driver -pub struct Cryp<'d, T: Instance, DmaIn = NoDma, DmaOut = NoDma> { - _peripheral: PeripheralRef<'d, T>, - indma: PeripheralRef<'d, DmaIn>, - outdma: PeripheralRef<'d, DmaOut>, +pub struct Cryp<'d, T: Instance, M: Mode> { + _peripheral: Peri<'d, T>, + _phantom: PhantomData, + indma: Option>, + outdma: Option>, } -impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { - /// Create a new CRYP driver. - pub fn new( - peri: impl Peripheral

+ 'd, - indma: impl Peripheral

+ 'd, - outdma: impl Peripheral

+ 'd, +impl<'d, T: Instance> Cryp<'d, T, Blocking> { + /// Create a new CRYP driver in blocking mode. + pub fn new_blocking( + peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { rcc::enable_and_reset::(); - into_ref!(peri, indma, outdma); let instance = Self { _peripheral: peri, - indma: indma, - outdma: outdma, + _phantom: PhantomData, + indma: None, + outdma: None, }; T::Interrupt::unpend(); @@ -1034,7 +1013,9 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { instance } +} +impl<'d, T: Instance, M: Mode> Cryp<'d, T, M> { /// Start a new encrypt or decrypt operation for the given cipher. pub fn start_blocking<'c, C: Cipher<'c> + CipherSized + IVSized>( &self, @@ -1114,89 +1095,6 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { ctx } - /// Start a new encrypt or decrypt operation for the given cipher. - pub async fn start<'c, C: Cipher<'c> + CipherSized + IVSized>( - &mut self, - cipher: &'c C, - dir: Direction, - ) -> Context<'c, C> - where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { - 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).await; - - self.store_context(&mut ctx); - - ctx - } - #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] /// Controls the header phase of cipher processing. /// This function is only valid for authenticated ciphers including GCM, CCM, and GMAC. @@ -1294,101 +1192,6 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { self.store_context(ctx); } - #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] - /// Controls the header phase of cipher processing. - /// This function is only valid for authenticated ciphers including GCM, CCM, and GMAC. - /// All additional associated data (AAD) must be supplied to this function prior to starting the payload phase with `payload`. - /// The AAD must be supplied in multiples of the block size (128-bits for AES, 64-bits for DES), except when supplying the last block. - /// When supplying the last block of AAD, `last_aad_block` must be `true`. - pub async fn aad<'c, const TAG_SIZE: usize, C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated>( - &mut self, - ctx: &mut Context<'c, C>, - aad: &[u8], - last_aad_block: bool, - ) where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { - 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 { - Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &ctx.aad_buffer).await; - assert_eq!(T::regs().sr().read().ifem(), true); - - // 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. - Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &ctx.aad_buffer).await; - assert_eq!(T::regs().sr().read().ifem(), true); - } - - // 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; - let start_index = len_to_copy; - let end_index = start_index + (C::BLOCK_SIZE * num_full_blocks); - Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &aad[start_index..end_index]).await; - - if last_aad_block { - if leftovers > 0 { - Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &ctx.aad_buffer).await; - assert_eq!(T::regs().sr().read().ifem(), true); - } - // 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`. @@ -1478,105 +1281,6 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { 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. - /// Output buffer must be at least as long as the input buffer. - /// 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 async fn payload<'c, C: Cipher<'c> + CipherSized + IVSized>( - &mut self, - ctx: &mut Context<'c, C>, - input: &[u8], - output: &mut [u8], - last_block: bool, - ) where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { - 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, cryp_v4))] - { - 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 index = block * C::BLOCK_SIZE; - // Read block out - let read = Self::read_bytes( - &mut self.outdma, - C::BLOCK_SIZE, - &mut output[index..index + C::BLOCK_SIZE], - ); - // Write block in - let write = Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &input[index..index + C::BLOCK_SIZE]); - embassy_futures::join::join(read, write).await; - } - - // 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(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 read = Self::read_bytes(&mut self.outdma, C::BLOCK_SIZE, &mut intermediate_data); - let write = Self::write_bytes(&mut self.indma, C::BLOCK_SIZE, &last_block); - embassy_futures::join::join(read, write).await; - - // 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(T::regs(), self, ctx.dir, &mut intermediate_data, temp1, mask) - .await; - } - - ctx.payload_len += input.len() as u64; - - self.store_context(ctx); - } - #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] /// Generates an authentication tag for authenticated ciphers including GCM, CCM, and GMAC. /// Called after the all data has been encrypted/decrypted by `payload`. @@ -1623,57 +1327,6 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { tag } - #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] - // Generates an authentication tag for authenticated ciphers including GCM, CCM, and GMAC. - /// Called after the all data has been encrypted/decrypted by `payload`. - pub async fn finish< - 'c, - const TAG_SIZE: usize, - C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated, - >( - &mut self, - mut ctx: Context<'c, C>, - ) -> [u8; TAG_SIZE] - where - DmaIn: crate::cryp::DmaIn, - DmaOut: crate::cryp::DmaOut, - { - 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)] - let footer: [u32; 4] = [ - headerlen1.swap_bytes(), - headerlen2.swap_bytes(), - payloadlen1.swap_bytes(), - payloadlen2.swap_bytes(), - ]; - #[cfg(any(cryp_v3, cryp_v4))] - let footer: [u32; 4] = [headerlen1, headerlen2, payloadlen1, payloadlen2]; - - let write = Self::write_words(&mut self.indma, C::BLOCK_SIZE, &footer); - - let mut full_tag: [u8; 16] = [0; 16]; - let read = Self::read_bytes(&mut self.outdma, C::BLOCK_SIZE, &mut full_tag); - - embassy_futures::join::join(read, write).await; - - 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; @@ -1774,31 +1427,6 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { } } - async fn write_bytes(dma: &mut PeripheralRef<'_, DmaIn>, block_size: usize, blocks: &[u8]) - where - DmaIn: crate::cryp::DmaIn, - { - if blocks.len() == 0 { - return; - } - // Ensure input is a multiple of block size. - assert_eq!(blocks.len() % block_size, 0); - // Configure DMA to transfer input to crypto core. - let dma_request = dma.request(); - let dst_ptr = T::regs().din().as_ptr(); - let num_words = blocks.len() / 4; - let src_ptr = ptr::slice_from_raw_parts(blocks.as_ptr().cast(), num_words); - let options = TransferOptions { - #[cfg(not(gpdma))] - priority: crate::dma::Priority::High, - ..Default::default() - }; - let dma_transfer = unsafe { Transfer::new_write_raw(dma, dma_request, src_ptr, dst_ptr, options) }; - T::regs().dmacr().modify(|w| w.set_dien(true)); - // Wait for the transfer to complete. - dma_transfer.await; - } - #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] fn write_words_blocking(&self, block_size: usize, blocks: &[u32]) { assert_eq!((blocks.len() * 4) % block_size, 0); @@ -1813,32 +1441,6 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { } } - #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] - async fn write_words(dma: &mut PeripheralRef<'_, DmaIn>, block_size: usize, blocks: &[u32]) - where - DmaIn: crate::cryp::DmaIn, - { - if blocks.len() == 0 { - return; - } - // Ensure input is a multiple of block size. - assert_eq!((blocks.len() * 4) % block_size, 0); - // Configure DMA to transfer input to crypto core. - let dma_request = dma.request(); - let dst_ptr = T::regs().din().as_ptr(); - let num_words = blocks.len(); - let src_ptr = ptr::slice_from_raw_parts(blocks.as_ptr().cast(), num_words); - let options = TransferOptions { - #[cfg(not(gpdma))] - priority: crate::dma::Priority::High, - ..Default::default() - }; - let dma_transfer = unsafe { Transfer::new_write_raw(dma, dma_request, src_ptr, dst_ptr, options) }; - T::regs().dmacr().modify(|w| w.set_dien(true)); - // Wait for the transfer to complete. - dma_transfer.await; - } - fn read_bytes_blocking(&self, block_size: usize, blocks: &mut [u8]) { // Block until there is output to read. while !T::regs().sr().read().ofne() {} @@ -1853,18 +1455,407 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { index += 4; } } +} - async fn read_bytes(dma: &mut PeripheralRef<'_, DmaOut>, block_size: usize, blocks: &mut [u8]) - where - DmaOut: crate::cryp::DmaOut, - { +impl<'d, T: Instance> Cryp<'d, T, Async> { + /// Create a new CRYP driver. + pub fn new( + peri: Peri<'d, T>, + indma: Peri<'d, impl DmaIn>, + outdma: Peri<'d, impl DmaOut>, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + rcc::enable_and_reset::(); + let instance = Self { + _peripheral: peri, + _phantom: PhantomData, + indma: new_dma!(indma), + outdma: new_dma!(outdma), + }; + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + instance + } + + /// Start a new encrypt or decrypt operation for the given cipher. + pub async fn start<'c, C: Cipher<'c> + CipherSized + IVSized>( + &mut 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).await; + + self.store_context(&mut ctx); + + ctx + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + /// Controls the header phase of cipher processing. + /// This function is only valid for authenticated ciphers including GCM, CCM, and GMAC. + /// All additional associated data (AAD) must be supplied to this function prior to starting the payload phase with `payload`. + /// The AAD must be supplied in multiples of the block size (128-bits for AES, 64-bits for DES), except when supplying the last block. + /// When supplying the last block of AAD, `last_aad_block` must be `true`. + pub async fn aad< + 'c, + const TAG_SIZE: usize, + C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated, + >( + &mut 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 { + Self::write_bytes(self.indma.as_mut().unwrap(), C::BLOCK_SIZE, &ctx.aad_buffer).await; + assert_eq!(T::regs().sr().read().ifem(), true); + + // 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. + Self::write_bytes(self.indma.as_mut().unwrap(), C::BLOCK_SIZE, &ctx.aad_buffer).await; + assert_eq!(T::regs().sr().read().ifem(), true); + } + + // 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; + let start_index = len_to_copy; + let end_index = start_index + (C::BLOCK_SIZE * num_full_blocks); + Self::write_bytes( + self.indma.as_mut().unwrap(), + C::BLOCK_SIZE, + &aad[start_index..end_index], + ) + .await; + + if last_aad_block { + if leftovers > 0 { + Self::write_bytes(self.indma.as_mut().unwrap(), C::BLOCK_SIZE, &ctx.aad_buffer).await; + assert_eq!(T::regs().sr().read().ifem(), true); + } + // 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. + /// Output buffer must be at least as long as the input buffer. + /// 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 async fn payload<'c, C: Cipher<'c> + CipherSized + IVSized>( + &mut 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, cryp_v4))] + { + 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 index = block * C::BLOCK_SIZE; + // Read block out + let read = Self::read_bytes( + self.outdma.as_mut().unwrap(), + C::BLOCK_SIZE, + &mut output[index..index + C::BLOCK_SIZE], + ); + // Write block in + let write = Self::write_bytes( + self.indma.as_mut().unwrap(), + C::BLOCK_SIZE, + &input[index..index + C::BLOCK_SIZE], + ); + embassy_futures::join::join(read, write).await; + } + + // 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(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 read = Self::read_bytes(self.outdma.as_mut().unwrap(), C::BLOCK_SIZE, &mut intermediate_data); + let write = Self::write_bytes(self.indma.as_mut().unwrap(), C::BLOCK_SIZE, &last_block); + embassy_futures::join::join(read, write).await; + + // 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(T::regs(), self, ctx.dir, &mut intermediate_data, temp1, mask) + .await; + } + + ctx.payload_len += input.len() as u64; + + self.store_context(ctx); + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + // Generates an authentication tag for authenticated ciphers including GCM, CCM, and GMAC. + /// Called after the all data has been encrypted/decrypted by `payload`. + pub async fn finish< + 'c, + const TAG_SIZE: usize, + C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated, + >( + &mut 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)] + let footer: [u32; 4] = [ + headerlen1.swap_bytes(), + headerlen2.swap_bytes(), + payloadlen1.swap_bytes(), + payloadlen2.swap_bytes(), + ]; + #[cfg(any(cryp_v3, cryp_v4))] + let footer: [u32; 4] = [headerlen1, headerlen2, payloadlen1, payloadlen2]; + + let write = Self::write_words(self.indma.as_mut().unwrap(), C::BLOCK_SIZE, &footer); + + let mut full_tag: [u8; 16] = [0; 16]; + let read = Self::read_bytes(self.outdma.as_mut().unwrap(), C::BLOCK_SIZE, &mut full_tag); + + embassy_futures::join::join(read, write).await; + + 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 + } + + async fn write_bytes(dma: &mut ChannelAndRequest<'d>, block_size: usize, blocks: &[u8]) { + if blocks.len() == 0 { + return; + } + // Ensure input is a multiple of block size. + assert_eq!(blocks.len() % block_size, 0); + // Configure DMA to transfer input to crypto core. + let dst_ptr: *mut u32 = T::regs().din().as_ptr(); + let num_words = blocks.len() / 4; + let src_ptr: *const [u8] = ptr::slice_from_raw_parts(blocks.as_ptr().cast(), num_words); + let options = TransferOptions { + #[cfg(not(gpdma))] + priority: crate::dma::Priority::High, + ..Default::default() + }; + let dma_transfer = unsafe { dma.write_raw(src_ptr, dst_ptr, options) }; + T::regs().dmacr().modify(|w| w.set_dien(true)); + // Wait for the transfer to complete. + dma_transfer.await; + } + + #[cfg(any(cryp_v2, cryp_v3, cryp_v4))] + async fn write_words(dma: &mut ChannelAndRequest<'d>, block_size: usize, blocks: &[u32]) { + if blocks.len() == 0 { + return; + } + // Ensure input is a multiple of block size. + assert_eq!((blocks.len() * 4) % block_size, 0); + // Configure DMA to transfer input to crypto core. + let dst_ptr: *mut u32 = T::regs().din().as_ptr(); + let num_words = blocks.len(); + let src_ptr: *const [u32] = ptr::slice_from_raw_parts(blocks.as_ptr().cast(), num_words); + let options = TransferOptions { + #[cfg(not(gpdma))] + priority: crate::dma::Priority::High, + ..Default::default() + }; + let dma_transfer = unsafe { dma.write_raw(src_ptr, dst_ptr, options) }; + T::regs().dmacr().modify(|w| w.set_dien(true)); + // Wait for the transfer to complete. + dma_transfer.await; + } + + async fn read_bytes(dma: &mut ChannelAndRequest<'d>, block_size: usize, blocks: &mut [u8]) { if blocks.len() == 0 { return; } // Ensure input is a multiple of block size. assert_eq!(blocks.len() % block_size, 0); // Configure DMA to get output from crypto core. - let dma_request = dma.request(); let src_ptr = T::regs().dout().as_ptr(); let num_words = blocks.len() / 4; let dst_ptr = ptr::slice_from_raw_parts_mut(blocks.as_mut_ptr().cast(), num_words); @@ -1873,7 +1864,7 @@ impl<'d, T: Instance, DmaIn, DmaOut> Cryp<'d, T, DmaIn, DmaOut> { priority: crate::dma::Priority::VeryHigh, ..Default::default() }; - let dma_transfer = unsafe { Transfer::new_read_raw(dma, dma_request, src_ptr, dst_ptr, options) }; + let dma_transfer = unsafe { dma.read_raw(src_ptr, dst_ptr, options) }; T::regs().dmacr().modify(|w| w.set_doen(true)); // Wait for the transfer to complete. dma_transfer.await; @@ -1886,7 +1877,7 @@ trait SealedInstance { /// CRYP instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + crate::rcc::RccPeripheral + 'static + Send { /// Interrupt for this CRYP instance. type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-stm32/src/dac/mod.rs b/embassy-stm32/src/dac/mod.rs index 8bba5ded0..30046849b 100644 --- a/embassy-stm32/src/dac/mod.rs +++ b/embassy-stm32/src/dac/mod.rs @@ -3,15 +3,15 @@ use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, PeripheralRef}; - -use crate::dma::NoDma; +use crate::dma::ChannelAndRequest; +use crate::mode::{Async, Blocking, Mode as PeriMode}; #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] use crate::pac::dac; use crate::rcc::{self, RccPeripheral}; -use crate::{peripherals, Peripheral}; +use crate::{peripherals, Peri}; mod tsel; +use embassy_hal_internal::PeripheralType; pub use tsel::TriggerSel; /// Operating mode for DAC channel @@ -100,46 +100,34 @@ pub enum ValueArray<'a> { /// /// If you want to use both channels, either together or independently, /// create a [`Dac`] first and use it to access each channel. -pub struct DacChannel<'d, T: Instance, const N: u8, DMA = NoDma> { - phantom: PhantomData<&'d mut T>, +pub struct DacChannel<'d, T: Instance, C: Channel, M: PeriMode> { + phantom: PhantomData<&'d mut (T, C, M)>, #[allow(unused)] - dma: PeripheralRef<'d, DMA>, + dma: Option>, } /// DAC channel 1 type alias. -pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>; +pub type DacCh1<'d, T, M> = DacChannel<'d, T, Ch1, M>; /// DAC channel 2 type alias. -pub type DacCh2<'d, T, DMA = NoDma> = DacChannel<'d, T, 2, DMA>; - -impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { - const IDX: usize = (N - 1) as usize; +pub type DacCh2<'d, T, M> = DacChannel<'d, T, Ch2, M>; +impl<'d, T: Instance, C: Channel> DacChannel<'d, T, C, Async> { /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral. /// - /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. - /// /// The channel is enabled on creation and begin to drive the output pin. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will /// disable the channel; you must re-enable it with `enable()`. /// /// By default, triggering is disabled, but it can be enabled using /// [`DacChannel::set_trigger()`]. - pub fn new( - _peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, - pin: impl Peripheral

+ crate::gpio::Pin> + 'd, - ) -> Self { - into_ref!(dma, pin); + pub fn new(peri: Peri<'d, T>, dma: Peri<'d, impl Dma>, pin: Peri<'d, impl DacPin>) -> Self { pin.set_as_analog(); - rcc::enable_and_reset::(); - let mut dac = Self { - phantom: PhantomData, - dma, - }; - #[cfg(any(dac_v5, dac_v6, dac_v7))] - dac.set_hfsel(); - dac.enable(); - dac + Self::new_inner( + peri, + new_dma!(dma), + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + Mode::NormalExternalBuffered, + ) } /// Create a new `DacChannel` instance where the external output pin is not used, @@ -150,13 +138,97 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the /// channel; you must re-enable it with `enable()`. /// - /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. + /// By default, triggering is disabled, but it can be enabled using + /// [`DacChannel::set_trigger()`]. + #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] + pub fn new_internal(peri: Peri<'d, T>, dma: Peri<'d, impl Dma>) -> Self { + Self::new_inner(peri, new_dma!(dma), Mode::NormalInternalUnbuffered) + } + + /// Write `data` to this channel via DMA. + /// + /// To prevent delays or glitches when outputing a periodic waveform, the `circular` + /// flag can be set. This configures a circular DMA transfer that continually outputs + /// `data`. Note that for performance reasons in circular mode the transfer-complete + /// interrupt is disabled. + #[cfg(not(gpdma))] + pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) { + // Enable DAC and DMA + T::regs().cr().modify(|w| { + w.set_en(C::IDX, true); + w.set_dmaen(C::IDX, true); + }); + + let dma = self.dma.as_mut().unwrap(); + + let tx_options = crate::dma::TransferOptions { + circular, + half_transfer_ir: false, + complete_transfer_ir: !circular, + ..Default::default() + }; + + // Initiate the correct type of DMA transfer depending on what data is passed + let tx_f = match data { + ValueArray::Bit8(buf) => unsafe { dma.write(buf, T::regs().dhr8r(C::IDX).as_ptr() as *mut u8, tx_options) }, + ValueArray::Bit12Left(buf) => unsafe { + dma.write(buf, T::regs().dhr12l(C::IDX).as_ptr() as *mut u16, tx_options) + }, + ValueArray::Bit12Right(buf) => unsafe { + dma.write(buf, T::regs().dhr12r(C::IDX).as_ptr() as *mut u16, tx_options) + }, + }; + + tx_f.await; + + T::regs().cr().modify(|w| { + w.set_en(C::IDX, false); + w.set_dmaen(C::IDX, false); + }); + } +} + +impl<'d, T: Instance, C: Channel> DacChannel<'d, T, C, Blocking> { + /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral. + /// + /// The channel is enabled on creation and begin to drive the output pin. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will + /// disable the channel; you must re-enable it with `enable()`. + /// + /// By default, triggering is disabled, but it can be enabled using + /// [`DacChannel::set_trigger()`]. + pub fn new_blocking(peri: Peri<'d, T>, pin: Peri<'d, impl DacPin>) -> Self { + pin.set_as_analog(); + Self::new_inner( + peri, + None, + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + Mode::NormalExternalBuffered, + ) + } + + /// Create a new `DacChannel` instance where the external output pin is not used, + /// so the DAC can only be used to generate internal signals. + /// The GPIO pin is therefore available to be used for other functions. + /// + /// The channel is set to [`Mode::NormalInternalUnbuffered`] and enabled on creation. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the + /// channel; you must re-enable it with `enable()`. /// /// By default, triggering is disabled, but it can be enabled using /// [`DacChannel::set_trigger()`]. #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] - pub fn new_internal(_peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd) -> Self { - into_ref!(dma); + pub fn new_internal_blocking(peri: Peri<'d, T>) -> Self { + Self::new_inner(peri, None, Mode::NormalInternalUnbuffered) + } +} + +impl<'d, T: Instance, C: Channel, M: PeriMode> DacChannel<'d, T, C, M> { + fn new_inner( + _peri: Peri<'d, T>, + dma: Option>, + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] mode: Mode, + ) -> Self { rcc::enable_and_reset::(); let mut dac = Self { phantom: PhantomData, @@ -164,7 +236,8 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { }; #[cfg(any(dac_v5, dac_v6, dac_v7))] dac.set_hfsel(); - dac.set_mode(Mode::NormalInternalUnbuffered); + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + dac.set_mode(mode); dac.enable(); dac } @@ -173,7 +246,7 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { pub fn set_enable(&mut self, on: bool) { critical_section::with(|_| { T::regs().cr().modify(|reg| { - reg.set_en(Self::IDX, on); + reg.set_en(C::IDX, on); }); }); } @@ -194,8 +267,8 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { pub fn set_trigger(&mut self, source: TriggerSel) { critical_section::with(|_| { T::regs().cr().modify(|reg| { - reg.set_en(Self::IDX, false); - reg.set_tsel(Self::IDX, source as u8); + reg.set_en(C::IDX, false); + reg.set_tsel(C::IDX, source as u8); }); }); } @@ -204,7 +277,7 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { pub fn set_triggering(&mut self, on: bool) { critical_section::with(|_| { T::regs().cr().modify(|reg| { - reg.set_ten(Self::IDX, on); + reg.set_ten(C::IDX, on); }); }); } @@ -212,7 +285,7 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { /// Software trigger this channel. pub fn trigger(&mut self) { T::regs().swtrigr().write(|reg| { - reg.set_swtrig(Self::IDX, true); + reg.set_swtrig(C::IDX, true); }); } @@ -223,10 +296,10 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { pub fn set_mode(&mut self, mode: Mode) { critical_section::with(|_| { T::regs().cr().modify(|reg| { - reg.set_en(Self::IDX, false); + reg.set_en(C::IDX, false); }); T::regs().mcr().modify(|reg| { - reg.set_mode(Self::IDX, mode.mode()); + reg.set_mode(C::IDX, mode.mode()); }); }); } @@ -237,15 +310,15 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { /// it will be output after the next trigger. pub fn set(&mut self, value: Value) { match value { - Value::Bit8(v) => T::regs().dhr8r(Self::IDX).write(|reg| reg.set_dhr(v)), - Value::Bit12Left(v) => T::regs().dhr12l(Self::IDX).write(|reg| reg.set_dhr(v)), - Value::Bit12Right(v) => T::regs().dhr12r(Self::IDX).write(|reg| reg.set_dhr(v)), + Value::Bit8(v) => T::regs().dhr8r(C::IDX).write(|reg| reg.set_dhr(v)), + Value::Bit12Left(v) => T::regs().dhr12l(C::IDX).write(|reg| reg.set_dhr(v)), + Value::Bit12Right(v) => T::regs().dhr12r(C::IDX).write(|reg| reg.set_dhr(v)), } } /// Read the current output value of the DAC. pub fn read(&self) -> u16 { - T::regs().dor(Self::IDX).read().dor() + T::regs().dor(C::IDX).read().dor() } /// Set HFSEL as appropriate for the current peripheral clock frequency. @@ -279,82 +352,7 @@ impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { } } -macro_rules! impl_dma_methods { - ($n:literal, $trait:ident) => { - impl<'d, T: Instance, DMA> DacChannel<'d, T, $n, DMA> - where - DMA: $trait, - { - /// Write `data` to this channel via DMA. - /// - /// To prevent delays or glitches when outputing a periodic waveform, the `circular` - /// flag can be set. This configures a circular DMA transfer that continually outputs - /// `data`. Note that for performance reasons in circular mode the transfer-complete - /// interrupt is disabled. - #[cfg(not(gpdma))] - pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) { - // Enable DAC and DMA - T::regs().cr().modify(|w| { - w.set_en(Self::IDX, true); - w.set_dmaen(Self::IDX, true); - }); - - let tx_request = self.dma.request(); - let dma_channel = &mut self.dma; - - let tx_options = crate::dma::TransferOptions { - circular, - half_transfer_ir: false, - complete_transfer_ir: !circular, - ..Default::default() - }; - - // Initiate the correct type of DMA transfer depending on what data is passed - let tx_f = match data { - ValueArray::Bit8(buf) => unsafe { - crate::dma::Transfer::new_write( - dma_channel, - tx_request, - buf, - T::regs().dhr8r(Self::IDX).as_ptr() as *mut u8, - tx_options, - ) - }, - ValueArray::Bit12Left(buf) => unsafe { - crate::dma::Transfer::new_write( - dma_channel, - tx_request, - buf, - T::regs().dhr12l(Self::IDX).as_ptr() as *mut u16, - tx_options, - ) - }, - ValueArray::Bit12Right(buf) => unsafe { - crate::dma::Transfer::new_write( - dma_channel, - tx_request, - buf, - T::regs().dhr12r(Self::IDX).as_ptr() as *mut u16, - tx_options, - ) - }, - }; - - tx_f.await; - - T::regs().cr().modify(|w| { - w.set_en(Self::IDX, false); - w.set_dmaen(Self::IDX, false); - }); - } - } - }; -} - -impl_dma_methods!(1, DacDma1); -impl_dma_methods!(2, DacDma2); - -impl<'d, T: Instance, const N: u8, DMA> Drop for DacChannel<'d, T, N, DMA> { +impl<'d, T: Instance, C: Channel, M: PeriMode> Drop for DacChannel<'d, T, C, M> { fn drop(&mut self) { rcc::disable::(); } @@ -368,14 +366,14 @@ impl<'d, T: Instance, const N: u8, DMA> Drop for DacChannel<'d, T, N, DMA> { /// /// ```ignore /// // Pins may need to be changed for your specific device. -/// let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new(p.DAC1, NoDma, NoDma, p.PA4, p.PA5).split(); +/// let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new_blocking(p.DAC1, p.PA4, p.PA5).split(); /// ``` -pub struct Dac<'d, T: Instance, DMACh1 = NoDma, DMACh2 = NoDma> { - ch1: DacChannel<'d, T, 1, DMACh1>, - ch2: DacChannel<'d, T, 2, DMACh2>, +pub struct Dac<'d, T: Instance, M: PeriMode> { + ch1: DacChannel<'d, T, Ch1, M>, + ch2: DacChannel<'d, T, Ch2, M>, } -impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { +impl<'d, T: Instance> Dac<'d, T, Async> { /// Create a new `Dac` instance, consuming the underlying DAC peripheral. /// /// This struct allows you to access both channels of the DAC, where available. You can either @@ -389,37 +387,21 @@ impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` /// method on the underlying channels. pub fn new( - _peri: impl Peripheral

+ 'd, - dma_ch1: impl Peripheral

+ 'd, - dma_ch2: impl Peripheral

+ 'd, - pin_ch1: impl Peripheral

+ crate::gpio::Pin> + 'd, - pin_ch2: impl Peripheral

+ crate::gpio::Pin> + 'd, + peri: Peri<'d, T>, + dma_ch1: Peri<'d, impl Dma>, + dma_ch2: Peri<'d, impl Dma>, + pin_ch1: Peri<'d, impl DacPin + crate::gpio::Pin>, + pin_ch2: Peri<'d, impl DacPin + crate::gpio::Pin>, ) -> Self { - into_ref!(dma_ch1, dma_ch2, pin_ch1, pin_ch2); pin_ch1.set_as_analog(); pin_ch2.set_as_analog(); - - // Enable twice to increment the DAC refcount for each channel. - rcc::enable_and_reset::(); - rcc::enable_and_reset::(); - - let mut ch1 = DacCh1 { - phantom: PhantomData, - dma: dma_ch1, - }; - #[cfg(any(dac_v5, dac_v6, dac_v7))] - ch1.set_hfsel(); - ch1.enable(); - - let mut ch2 = DacCh2 { - phantom: PhantomData, - dma: dma_ch2, - }; - #[cfg(any(dac_v5, dac_v6, dac_v7))] - ch2.set_hfsel(); - ch2.enable(); - - Self { ch1, ch2 } + Self::new_inner( + peri, + new_dma!(dma_ch1), + new_dma!(dma_ch2), + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + Mode::NormalExternalBuffered, + ) } /// Create a new `Dac` instance where the external output pins are not used, @@ -438,11 +420,75 @@ impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { /// method on the underlying channels. #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] pub fn new_internal( - _peri: impl Peripheral

+ 'd, - dma_ch1: impl Peripheral

+ 'd, - dma_ch2: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma_ch1: Peri<'d, impl Dma>, + dma_ch2: Peri<'d, impl Dma>, + ) -> Self { + Self::new_inner( + peri, + new_dma!(dma_ch1), + new_dma!(dma_ch2), + Mode::NormalInternalUnbuffered, + ) + } +} + +impl<'d, T: Instance> Dac<'d, T, Blocking> { + /// Create a new `Dac` instance, consuming the underlying DAC peripheral. + /// + /// This struct allows you to access both channels of the DAC, where available. You can either + /// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use + /// the two channels together. + /// + /// The channels are enabled on creation and begin to drive their output pins. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will + /// disable the channel; you must re-enable them with `enable()`. + /// + /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` + /// method on the underlying channels. + pub fn new_blocking( + peri: Peri<'d, T>, + pin_ch1: Peri<'d, impl DacPin + crate::gpio::Pin>, + pin_ch2: Peri<'d, impl DacPin + crate::gpio::Pin>, + ) -> Self { + pin_ch1.set_as_analog(); + pin_ch2.set_as_analog(); + Self::new_inner( + peri, + None, + None, + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + Mode::NormalExternalBuffered, + ) + } + + /// Create a new `Dac` instance where the external output pins are not used, + /// so the DAC can only be used to generate internal signals but the GPIO + /// pins remain available for other functions. + /// + /// This struct allows you to access both channels of the DAC, where available. You can either + /// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use the two + /// channels together. + /// + /// The channels are set to [`Mode::NormalInternalUnbuffered`] and enabled on creation. + /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the + /// channel; you must re-enable them with `enable()`. + /// + /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` + /// method on the underlying channels. + #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] + pub fn new_internal(peri: Peri<'d, T>) -> Self { + Self::new_inner(peri, None, None, Mode::NormalInternalUnbuffered) + } +} + +impl<'d, T: Instance, M: PeriMode> Dac<'d, T, M> { + fn new_inner( + _peri: Peri<'d, T>, + dma_ch1: Option>, + dma_ch2: Option>, + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] mode: Mode, ) -> Self { - into_ref!(dma_ch1, dma_ch2); // Enable twice to increment the DAC refcount for each channel. rcc::enable_and_reset::(); rcc::enable_and_reset::(); @@ -453,7 +499,8 @@ impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { }; #[cfg(any(dac_v5, dac_v6, dac_v7))] ch1.set_hfsel(); - ch1.set_mode(Mode::NormalInternalUnbuffered); + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + ch1.set_mode(mode); ch1.enable(); let mut ch2 = DacCh2 { @@ -462,7 +509,8 @@ impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { }; #[cfg(any(dac_v5, dac_v6, dac_v7))] ch2.set_hfsel(); - ch2.set_mode(Mode::NormalInternalUnbuffered); + #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] + ch2.set_mode(mode); ch2.enable(); Self { ch1, ch2 } @@ -471,17 +519,17 @@ impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { /// Split this `Dac` into separate channels. /// /// You can access and move the channels around separately after splitting. - pub fn split(self) -> (DacCh1<'d, T, DMACh1>, DacCh2<'d, T, DMACh2>) { + pub fn split(self) -> (DacCh1<'d, T, M>, DacCh2<'d, T, M>) { (self.ch1, self.ch2) } /// Temporarily access channel 1. - pub fn ch1(&mut self) -> &mut DacCh1<'d, T, DMACh1> { + pub fn ch1(&mut self) -> &mut DacCh1<'d, T, M> { &mut self.ch1 } /// Temporarily access channel 2. - pub fn ch2(&mut self) -> &mut DacCh2<'d, T, DMACh2> { + pub fn ch2(&mut self) -> &mut DacCh2<'d, T, M> { &mut self.ch2 } @@ -513,12 +561,31 @@ trait SealedInstance { /// DAC instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + 'static {} -dma_trait!(DacDma1, Instance); -dma_trait!(DacDma2, Instance); +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static {} -/// Marks a pin that can be used with the DAC -pub trait DacPin: crate::gpio::Pin + 'static {} +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} + +trait SealedChannel { + const IDX: usize; +} +/// DAC channel trait. +#[allow(private_bounds)] +pub trait Channel: SealedChannel {} + +impl SealedChannel for Ch1 { + const IDX: usize = 0; +} +impl SealedChannel for Ch2 { + const IDX: usize = 1; +} +impl Channel for Ch1 {} +impl Channel for Ch2 {} + +dma_trait!(Dma, Instance, Channel); +pin_trait!(DacPin, Instance, Channel); foreach_peripheral!( (dac, $inst:ident) => { @@ -531,9 +598,3 @@ foreach_peripheral!( impl crate::dac::Instance for peripherals::$inst {} }; ); - -macro_rules! impl_dac_pin { - ($inst:ident, $pin:ident, $ch:expr) => { - impl crate::dac::DacPin for crate::peripherals::$pin {} - }; -} diff --git a/embassy-stm32/src/dcmi.rs b/embassy-stm32/src/dcmi.rs index 4ba4e824e..d05faee21 100644 --- a/embassy-stm32/src/dcmi.rs +++ b/embassy-stm32/src/dcmi.rs @@ -3,13 +3,13 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use crate::dma::Transfer; use crate::gpio::{AfType, Pull}; use crate::interrupt::typelevel::Interrupt; -use crate::{interrupt, rcc, Peripheral}; +use crate::{interrupt, rcc, Peri}; /// Interrupt handler. pub struct InterruptHandler { @@ -106,8 +106,7 @@ impl Default for Config { macro_rules! config_pins { ($($pin:ident),*) => { - into_ref!($($pin),*); - critical_section::with(|_| { + critical_section::with(|_| { $( $pin.set_as_af($pin.af_num(), AfType::input(Pull::None)); )* @@ -117,8 +116,8 @@ macro_rules! config_pins { /// DCMI driver. pub struct Dcmi<'d, T: Instance, Dma: FrameDma> { - inner: PeripheralRef<'d, T>, - dma: PeripheralRef<'d, Dma>, + inner: Peri<'d, T>, + dma: Peri<'d, Dma>, } impl<'d, T, Dma> Dcmi<'d, T, Dma> @@ -128,23 +127,22 @@ where { /// Create a new DCMI driver with 8 data bits. pub fn new_8bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - v_sync: impl Peripheral

> + 'd, - h_sync: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + v_sync: Peri<'d, impl VSyncPin>, + h_sync: Peri<'d, impl HSyncPin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7); config_pins!(v_sync, h_sync, pixclk); @@ -153,25 +151,24 @@ where /// Create a new DCMI driver with 10 data bits. pub fn new_10bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - d8: impl Peripheral

> + 'd, - d9: impl Peripheral

> + 'd, - v_sync: impl Peripheral

> + 'd, - h_sync: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + d8: Peri<'d, impl D8Pin>, + d9: Peri<'d, impl D9Pin>, + v_sync: Peri<'d, impl VSyncPin>, + h_sync: Peri<'d, impl HSyncPin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); config_pins!(v_sync, h_sync, pixclk); @@ -180,27 +177,26 @@ where /// Create a new DCMI driver with 12 data bits. pub fn new_12bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - d8: impl Peripheral

> + 'd, - d9: impl Peripheral

> + 'd, - d10: impl Peripheral

> + 'd, - d11: impl Peripheral

> + 'd, - v_sync: impl Peripheral

> + 'd, - h_sync: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + d8: Peri<'d, impl D8Pin>, + d9: Peri<'d, impl D9Pin>, + d10: Peri<'d, impl D10Pin>, + d11: Peri<'d, impl D11Pin>, + v_sync: Peri<'d, impl VSyncPin>, + h_sync: Peri<'d, impl HSyncPin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11); config_pins!(v_sync, h_sync, pixclk); @@ -209,29 +205,28 @@ where /// Create a new DCMI driver with 14 data bits. pub fn new_14bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - d8: impl Peripheral

> + 'd, - d9: impl Peripheral

> + 'd, - d10: impl Peripheral

> + 'd, - d11: impl Peripheral

> + 'd, - d12: impl Peripheral

> + 'd, - d13: impl Peripheral

> + 'd, - v_sync: impl Peripheral

> + 'd, - h_sync: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + d8: Peri<'d, impl D8Pin>, + d9: Peri<'d, impl D9Pin>, + d10: Peri<'d, impl D10Pin>, + d11: Peri<'d, impl D11Pin>, + d12: Peri<'d, impl D12Pin>, + d13: Peri<'d, impl D13Pin>, + v_sync: Peri<'d, impl VSyncPin>, + h_sync: Peri<'d, impl HSyncPin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13); config_pins!(v_sync, h_sync, pixclk); @@ -240,21 +235,20 @@ where /// Create a new DCMI driver with 8 data bits, with embedded synchronization. pub fn new_es_8bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7); config_pins!(pixclk); @@ -263,23 +257,22 @@ where /// Create a new DCMI driver with 10 data bits, with embedded synchronization. pub fn new_es_10bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - d8: impl Peripheral

> + 'd, - d9: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + d8: Peri<'d, impl D8Pin>, + d9: Peri<'d, impl D9Pin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); config_pins!(pixclk); @@ -288,25 +281,24 @@ where /// Create a new DCMI driver with 12 data bits, with embedded synchronization. pub fn new_es_12bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - d8: impl Peripheral

> + 'd, - d9: impl Peripheral

> + 'd, - d10: impl Peripheral

> + 'd, - d11: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + d8: Peri<'d, impl D8Pin>, + d9: Peri<'d, impl D9Pin>, + d10: Peri<'d, impl D10Pin>, + d11: Peri<'d, impl D11Pin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11); config_pins!(pixclk); @@ -315,27 +307,26 @@ where /// Create a new DCMI driver with 14 data bits, with embedded synchronization. pub fn new_es_14bit( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, _irq: impl interrupt::typelevel::Binding> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - d8: impl Peripheral

> + 'd, - d9: impl Peripheral

> + 'd, - d10: impl Peripheral

> + 'd, - d11: impl Peripheral

> + 'd, - d12: impl Peripheral

> + 'd, - d13: impl Peripheral

> + 'd, - pixclk: impl Peripheral

> + 'd, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + d8: Peri<'d, impl D8Pin>, + d9: Peri<'d, impl D9Pin>, + d10: Peri<'d, impl D10Pin>, + d11: Peri<'d, impl D11Pin>, + d12: Peri<'d, impl D12Pin>, + d13: Peri<'d, impl D13Pin>, + pixclk: Peri<'d, impl PixClkPin>, config: Config, ) -> Self { - into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13); config_pins!(pixclk); @@ -343,8 +334,8 @@ where } fn new_inner( - peri: PeripheralRef<'d, T>, - dma: PeripheralRef<'d, Dma>, + peri: Peri<'d, T>, + dma: Peri<'d, Dma>, config: Config, use_embedded_synchronization: bool, edm: u8, @@ -396,7 +387,7 @@ where let r = self.inner.regs(); let src = r.dr().as_ptr() as *mut u32; let request = self.dma.request(); - let dma_read = unsafe { Transfer::new_read(&mut self.dma, request, src, buffer, Default::default()) }; + let dma_read = unsafe { Transfer::new_read(self.dma.reborrow(), request, src, buffer, Default::default()) }; Self::clear_interrupt_flags(); Self::enable_irqs(); @@ -435,7 +426,7 @@ trait SealedInstance: crate::rcc::RccPeripheral { /// DCMI instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static { +pub trait Instance: SealedInstance + PeripheralType + 'static { /// Interrupt for this instance. type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index 8a6aa53a0..7dbbe7b72 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -3,10 +3,10 @@ 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_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; -use super::ringbuffer::{DmaCtrl, OverrunError, ReadableDmaRingBuffer, WritableDmaRingBuffer}; +use super::ringbuffer::{DmaCtrl, Error, ReadableDmaRingBuffer, WritableDmaRingBuffer}; use super::word::{Word, WordSize}; use super::{AnyChannel, Channel, Dir, Request, STATE}; use crate::interrupt::typelevel::Interrupt; @@ -15,6 +15,8 @@ use crate::{interrupt, pac}; pub(crate) struct ChannelInfo { pub(crate) dma: DmaInfo, pub(crate) num: usize, + #[cfg(feature = "_dual-core")] + pub(crate) irq: pac::Interrupt, #[cfg(dmamux)] pub(crate) dmamux: super::DmamuxInfo, } @@ -98,7 +100,7 @@ impl From for pac::dma::vals::Pl { Priority::Low => pac::dma::vals::Pl::LOW, Priority::Medium => pac::dma::vals::Pl::MEDIUM, Priority::High => pac::dma::vals::Pl::HIGH, - Priority::VeryHigh => pac::dma::vals::Pl::VERYHIGH, + Priority::VeryHigh => pac::dma::vals::Pl::VERY_HIGH, } } } @@ -110,7 +112,7 @@ impl From for pac::bdma::vals::Pl { Priority::Low => pac::bdma::vals::Pl::LOW, Priority::Medium => pac::bdma::vals::Pl::MEDIUM, Priority::High => pac::bdma::vals::Pl::HIGH, - Priority::VeryHigh => pac::bdma::vals::Pl::VERYHIGH, + Priority::VeryHigh => pac::bdma::vals::Pl::VERY_HIGH, } } } @@ -136,8 +138,8 @@ mod dma_only { impl From

for vals::Dir { fn from(raw: Dir) -> Self { match raw { - Dir::MemoryToPeripheral => Self::MEMORYTOPERIPHERAL, - Dir::PeripheralToMemory => Self::PERIPHERALTOMEMORY, + Dir::MemoryToPeripheral => Self::MEMORY_TO_PERIPHERAL, + Dir::PeripheralToMemory => Self::PERIPHERAL_TO_MEMORY, } } } @@ -205,7 +207,7 @@ mod dma_only { match value { FifoThreshold::Quarter => vals::Fth::QUARTER, FifoThreshold::Half => vals::Fth::HALF, - FifoThreshold::ThreeQuarters => vals::Fth::THREEQUARTERS, + FifoThreshold::ThreeQuarters => vals::Fth::THREE_QUARTERS, FifoThreshold::Full => vals::Fth::FULL, } } @@ -231,8 +233,8 @@ mod bdma_only { impl From for vals::Dir { fn from(raw: Dir) -> Self { match raw { - Dir::MemoryToPeripheral => Self::FROMMEMORY, - Dir::PeripheralToMemory => Self::FROMPERIPHERAL, + Dir::MemoryToPeripheral => Self::FROM_MEMORY, + Dir::PeripheralToMemory => Self::FROM_PERIPHERAL, } } } @@ -259,10 +261,12 @@ pub(crate) unsafe fn init( foreach_interrupt! { ($peri:ident, dma, $block:ident, $signal_name:ident, $irq:ident) => { crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, dma_priority); + #[cfg(not(feature = "_dual-core"))] 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); + #[cfg(not(feature = "_dual-core"))] crate::interrupt::typelevel::$irq::enable(); }; } @@ -295,7 +299,6 @@ impl AnyChannel { } else { return; } - state.waker.wake(); } #[cfg(bdma)] @@ -337,10 +340,16 @@ impl AnyChannel { mem_addr: *mut u32, mem_len: usize, incr_mem: bool, - data_size: WordSize, + mem_size: WordSize, + peripheral_size: WordSize, options: TransferOptions, ) { let info = self.info(); + #[cfg(feature = "_dual-core")] + { + use embassy_hal_internal::interrupt::InterruptExt as _; + info.irq.enable(); + } #[cfg(dmamux)] super::dmamux::configure_dmamux(&info.dmamux, _request); @@ -350,11 +359,13 @@ impl AnyChannel { match self.info().dma { #[cfg(dma)] DmaInfo::Dma(r) => { + let state: &ChannelState = &STATE[self.id as usize]; let ch = r.st(info.num); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); + state.complete_count.store(0, Ordering::Release); self.clear_irqs(); ch.par().write_value(peri_addr as u32); @@ -372,8 +383,8 @@ impl AnyChannel { }); ch.cr().write(|w| { w.set_dir(dir.into()); - w.set_msize(data_size.into()); - w.set_psize(data_size.into()); + w.set_msize(mem_size.into()); + w.set_psize(peripheral_size.into()); w.set_pl(options.priority.into()); w.set_minc(incr_mem); w.set_pinc(false); @@ -406,8 +417,8 @@ impl AnyChannel { 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_psize(peripheral_size.into()); + w.set_msize(mem_size.into()); w.set_minc(incr_mem); w.set_dir(dir.into()); w.set_teie(true); @@ -484,6 +495,26 @@ impl AnyChannel { } } + fn request_pause(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + // Disable the channel without overwriting the existing configuration + r.st(info.num).cr().modify(|w| { + w.set_en(false); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + // Disable the channel without overwriting the existing configuration + r.ch(info.num).cr().modify(|w| { + w.set_en(false); + }); + } + } + } + fn is_running(&self) -> bool { let info = self.info(); match self.info().dma { @@ -540,13 +571,13 @@ impl AnyChannel { /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { - channel: PeripheralRef<'a, AnyChannel>, + channel: Peri<'a, AnyChannel>, } impl<'a> Transfer<'a> { /// Create a new read DMA transfer (peripheral to memory). pub unsafe fn new_read( - channel: impl Peripheral

+ 'a, + channel: Peri<'a, impl Channel>, request: Request, peri_addr: *mut W, buf: &'a mut [W], @@ -557,16 +588,14 @@ impl<'a> Transfer<'a> { /// Create a new read DMA transfer (peripheral to memory), using raw pointers. pub unsafe fn new_read_raw( - channel: impl Peripheral

+ 'a, + channel: Peri<'a, impl Channel>, request: Request, peri_addr: *mut W, buf: *mut [W], options: TransferOptions, ) -> Self { - into_ref!(channel); - Self::new_inner( - channel.map_into(), + channel.into(), request, Dir::PeripheralToMemory, peri_addr as *const u32, @@ -574,57 +603,55 @@ impl<'a> Transfer<'a> { buf.len(), true, W::size(), + W::size(), options, ) } /// Create a new write DMA transfer (memory to peripheral). - pub unsafe fn new_write( - channel: impl Peripheral

+ 'a, + pub unsafe fn new_write( + channel: Peri<'a, impl Channel>, request: Request, - buf: &'a [W], - peri_addr: *mut W, + buf: &'a [MW], + peri_addr: *mut PW, 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( - channel: impl Peripheral

+ 'a, + pub unsafe fn new_write_raw( + channel: Peri<'a, impl Channel>, request: Request, - buf: *const [W], - peri_addr: *mut W, + buf: *const [MW], + peri_addr: *mut PW, options: TransferOptions, ) -> Self { - into_ref!(channel); - Self::new_inner( - channel.map_into(), + channel.into(), request, Dir::MemoryToPeripheral, peri_addr as *const u32, - buf as *const W as *mut u32, + buf as *const MW as *mut u32, buf.len(), true, - W::size(), + MW::size(), + PW::size(), options, ) } /// Create a new write DMA transfer (memory to peripheral), writing the same value repeatedly. pub unsafe fn new_write_repeated( - channel: impl Peripheral

+ 'a, + channel: Peri<'a, impl Channel>, request: Request, repeated: &'a W, count: usize, peri_addr: *mut W, options: TransferOptions, ) -> Self { - into_ref!(channel); - Self::new_inner( - channel.map_into(), + channel.into(), request, Dir::MemoryToPeripheral, peri_addr as *const u32, @@ -632,12 +659,13 @@ impl<'a> Transfer<'a> { count, false, W::size(), + W::size(), options, ) } unsafe fn new_inner( - channel: PeripheralRef<'a, AnyChannel>, + channel: Peri<'a, AnyChannel>, _request: Request, dir: Dir, peri_addr: *const u32, @@ -645,25 +673,43 @@ impl<'a> Transfer<'a> { mem_len: usize, incr_mem: bool, data_size: WordSize, + peripheral_size: WordSize, options: TransferOptions, ) -> Self { assert!(mem_len > 0 && mem_len <= 0xFFFF); channel.configure( - _request, dir, peri_addr, mem_addr, mem_len, incr_mem, data_size, options, + _request, + dir, + peri_addr, + mem_addr, + mem_len, + incr_mem, + data_size, + peripheral_size, + options, ); channel.start(); - Self { channel } } /// Request the transfer to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// 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() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether this transfer is still running. /// /// If this returns `false`, it can be because either the transfer finished, or @@ -717,17 +763,13 @@ impl<'a> Future for Transfer<'a> { // ============================== -struct DmaCtrlImpl<'a>(PeripheralRef<'a, AnyChannel>); +struct DmaCtrlImpl<'a>(Peri<'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))] @@ -747,27 +789,27 @@ impl<'a> DmaCtrl for DmaCtrlImpl<'a> { /// Ringbuffer for receiving data using DMA circular mode. pub struct ReadableRingBuffer<'a, W: Word> { - channel: PeripheralRef<'a, AnyChannel>, + channel: Peri<'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

+ 'a, + channel: Peri<'a, impl Channel>, _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 channel: Peri<'a, AnyChannel> = channel.into(); let buffer_ptr = buffer.as_mut_ptr(); let len = buffer.len(); let dir = Dir::PeripheralToMemory; let data_size = W::size(); + options.half_transfer_ir = true; options.complete_transfer_ir = true; options.circular = true; @@ -779,6 +821,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { len, true, data_size, + data_size, options, ); @@ -792,27 +835,27 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { /// /// You must call this after creating it for it to work. pub fn start(&mut self) { - self.channel.start() + self.channel.start(); } /// Clear all data in the ring buffer. pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.reset(&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> { + /// Error 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), Error> { 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. + /// Error 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, @@ -820,12 +863,17 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { /// 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 { + pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result { self.ringbuf .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) .await } + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + /// The capacity of the ringbuffer pub const fn capacity(&self) -> usize { self.ringbuf.cap() @@ -836,13 +884,23 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } - /// Request DMA to stop. + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// 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() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether DMA is still running. /// /// If this returns `false`, it can be because either the transfer finished, or @@ -883,21 +941,20 @@ impl<'a, W: Word> Drop for ReadableRingBuffer<'a, W> { /// Ringbuffer for writing data using DMA circular mode. pub struct WritableRingBuffer<'a, W: Word> { - channel: PeripheralRef<'a, AnyChannel>, + channel: Peri<'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

+ 'a, + channel: Peri<'a, impl Channel>, _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 channel: Peri<'a, AnyChannel> = channel.into(); let len = buffer.len(); let dir = Dir::MemoryToPeripheral; @@ -916,6 +973,7 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { len, true, data_size, + data_size, options, ); @@ -929,34 +987,45 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { /// /// You must call this after creating it for it to work. pub fn start(&mut self) { - self.channel.start() + self.channel.start(); } /// Clear all data in the ring buffer. pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); + self.ringbuf.reset(&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> { + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { 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> { + pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { 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 { + pub async fn write_exact(&mut self, buffer: &[W]) -> Result { self.ringbuf .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) .await } + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self) -> Result { + self.ringbuf + .wait_write_error(&mut DmaCtrlImpl(self.channel.reborrow())) + .await + } + + /// The current length of the ringbuffer + pub fn len(&mut self) -> Result { + Ok(self.ringbuf.len(&mut DmaCtrlImpl(self.channel.reborrow()))?) + } + /// The capacity of the ringbuffer pub const fn capacity(&self) -> usize { self.ringbuf.cap() @@ -967,13 +1036,23 @@ impl<'a, W: Word> WritableRingBuffer<'a, W> { DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); } - /// Request DMA to stop. + /// Request the DMA to stop. + /// The configuration for this channel will **not be preserved**. If you need to restart the transfer + /// at a later point with the same configuration, see [`request_pause`](Self::request_pause) instead. /// /// 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() } + /// Request the transfer to pause, keeping the existing configuration for this channel. + /// To restart the transfer, call [`start`](Self::start) again. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_pause(&mut self) { + self.channel.request_pause() + } + /// Return whether DMA is still running. /// /// If this returns `false`, it can be because either the transfer finished, or diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index 13d5d15be..ade70fb55 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -5,7 +5,7 @@ use core::pin::Pin; use core::sync::atomic::{fence, Ordering}; use core::task::{Context, Poll}; -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; use super::word::{Word, WordSize}; @@ -18,6 +18,8 @@ use crate::pac::gpdma::vals; pub(crate) struct ChannelInfo { pub(crate) dma: pac::gpdma::Gpdma, pub(crate) num: usize, + #[cfg(feature = "_dual-core")] + pub(crate) irq: pac::Interrupt, } /// GPDMA transfer options. @@ -36,7 +38,7 @@ impl From for vals::Dw { fn from(raw: WordSize) -> Self { match raw { WordSize::OneByte => Self::BYTE, - WordSize::TwoBytes => Self::HALFWORD, + WordSize::TwoBytes => Self::HALF_WORD, WordSize::FourBytes => Self::WORD, } } @@ -57,6 +59,7 @@ pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: P foreach_interrupt! { ($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => { crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, irq_priority); + #[cfg(not(feature = "_dual-core"))] crate::interrupt::typelevel::$irq::enable(); }; } @@ -67,6 +70,12 @@ 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(); + #[cfg(feature = "_dual-core")] + { + use embassy_hal_internal::interrupt::InterruptExt as _; + info.irq.enable(); + } + let state = &STATE[self.id as usize]; let ch = info.dma.ch(info.num); @@ -100,13 +109,13 @@ impl AnyChannel { /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Transfer<'a> { - channel: PeripheralRef<'a, AnyChannel>, + channel: Peri<'a, AnyChannel>, } impl<'a> Transfer<'a> { /// Create a new read DMA transfer (peripheral to memory). pub unsafe fn new_read( - channel: impl Peripheral

+ 'a, + channel: Peri<'a, impl Channel>, request: Request, peri_addr: *mut W, buf: &'a mut [W], @@ -117,16 +126,14 @@ impl<'a> Transfer<'a> { /// Create a new read DMA transfer (peripheral to memory), using raw pointers. pub unsafe fn new_read_raw( - channel: impl Peripheral

+ 'a, + channel: Peri<'a, impl Channel>, request: Request, peri_addr: *mut W, buf: *mut [W], options: TransferOptions, ) -> Self { - into_ref!(channel); - Self::new_inner( - channel.map_into(), + channel.into(), request, Dir::PeripheralToMemory, peri_addr as *const u32, @@ -134,70 +141,69 @@ impl<'a> Transfer<'a> { buf.len(), true, W::size(), + W::size(), options, ) } /// Create a new write DMA transfer (memory to peripheral). - pub unsafe fn new_write( - channel: impl Peripheral

+ 'a, + pub unsafe fn new_write( + channel: Peri<'a, impl Channel>, request: Request, - buf: &'a [W], - peri_addr: *mut W, + buf: &'a [MW], + peri_addr: *mut PW, 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( - channel: impl Peripheral

+ 'a, + pub unsafe fn new_write_raw( + channel: Peri<'a, impl Channel>, request: Request, - buf: *const [W], - peri_addr: *mut W, + buf: *const [MW], + peri_addr: *mut PW, options: TransferOptions, ) -> Self { - into_ref!(channel); - Self::new_inner( - channel.map_into(), + channel.into(), request, Dir::MemoryToPeripheral, peri_addr as *const u32, - buf as *const W as *mut u32, + buf as *const MW as *mut u32, buf.len(), true, - W::size(), + MW::size(), + PW::size(), options, ) } /// Create a new write DMA transfer (memory to peripheral), writing the same value repeatedly. - pub unsafe fn new_write_repeated( - channel: impl Peripheral

+ 'a, + pub unsafe fn new_write_repeated( + channel: Peri<'a, impl Channel>, request: Request, - repeated: &'a W, + repeated: &'a MW, count: usize, - peri_addr: *mut W, + peri_addr: *mut PW, options: TransferOptions, ) -> Self { - into_ref!(channel); - Self::new_inner( - channel.map_into(), + channel.into(), request, Dir::MemoryToPeripheral, peri_addr as *const u32, - repeated as *const W as *mut u32, + repeated as *const MW as *mut u32, count, false, - W::size(), + MW::size(), + PW::size(), options, ) } unsafe fn new_inner( - channel: PeripheralRef<'a, AnyChannel>, + channel: Peri<'a, AnyChannel>, request: Request, dir: Dir, peri_addr: *const u32, @@ -205,9 +211,13 @@ impl<'a> Transfer<'a> { mem_len: usize, incr_mem: bool, data_size: WordSize, + dst_size: WordSize, _options: TransferOptions, ) -> Self { - assert!(mem_len > 0 && mem_len <= 0xFFFF); + // BNDT is specified as bytes, not as number of transfers. + let Ok(bndt) = (mem_len * data_size.bytes()).try_into() else { + panic!("DMA transfers may not be larger than 65535 bytes."); + }; let info = channel.info(); let ch = info.dma.ch(info.num); @@ -217,29 +227,24 @@ impl<'a> Transfer<'a> { let this = Self { channel }; - #[cfg(dmamux)] - 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 ch.llr().write(|_| {}); // no linked list ch.tr1().write(|w| { w.set_sdw(data_size.into()); - w.set_ddw(data_size.into()); + w.set_ddw(dst_size.into()); w.set_sinc(dir == Dir::MemoryToPeripheral && incr_mem); w.set_dinc(dir == Dir::PeripheralToMemory && incr_mem); }); ch.tr2().write(|w| { w.set_dreq(match dir { - Dir::MemoryToPeripheral => vals::Dreq::DESTINATIONPERIPHERAL, - Dir::PeripheralToMemory => vals::Dreq::SOURCEPERIPHERAL, + Dir::MemoryToPeripheral => vals::Dreq::DESTINATION_PERIPHERAL, + Dir::PeripheralToMemory => vals::Dreq::SOURCE_PERIPHERAL, }); w.set_reqsel(request); }); - ch.br1().write(|w| { - // BNDT is specified as bytes, not as number of transfers. - w.set_bndt((mem_len * data_size.bytes()) as u16) - }); + ch.tr3().write(|_| {}); // no address offsets. + ch.br1().write(|w| w.set_bndt(bndt)); match dir { Dir::MemoryToPeripheral => { diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index 66c4aa53c..d3b070a6d 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -22,7 +22,7 @@ pub(crate) use util::*; pub(crate) mod ringbuffer; pub mod word; -use embassy_hal_internal::{impl_peripheral, Peripheral}; +use embassy_hal_internal::{impl_peripheral, PeripheralType}; use crate::interrupt; @@ -51,17 +51,7 @@ pub(crate) trait ChannelInterrupt { /// DMA channel. #[allow(private_bounds)] -pub trait Channel: SealedChannel + Peripheral

+ Into + '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() } - } -} +pub trait Channel: SealedChannel + PeripheralType + Into + 'static {} macro_rules! dma_channel_impl { ($channel_peri:ident, $index:expr) => { @@ -79,8 +69,10 @@ macro_rules! dma_channel_impl { impl crate::dma::Channel for crate::peripherals::$channel_peri {} impl From for crate::dma::AnyChannel { - fn from(x: crate::peripherals::$channel_peri) -> Self { - crate::dma::Channel::degrade(x) + fn from(val: crate::peripherals::$channel_peri) -> Self { + Self { + id: crate::dma::SealedChannel::id(&val), + } } } }; @@ -108,17 +100,6 @@ 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 -/// to indicate it should not use DMA. -/// -/// This often causes async functionality to not be available on the instance, -/// leaving only blocking functionality. -pub struct NoDma; - -impl_peripheral!(NoDma); - // safety: must be called only once at startup pub(crate) unsafe fn init( cs: critical_section::CriticalSection, diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs deleted file mode 100644 index 23f1d67d5..000000000 --- a/embassy-stm32/src/dma/ringbuffer.rs +++ /dev/null @@ -1,668 +0,0 @@ -#![cfg_attr(gpdma, allow(unused))] - -use core::future::poll_fn; -use core::ops::Range; -use core::sync::atomic::{compiler_fence, Ordering}; -use core::task::{Poll, Waker}; - -use super::word::Word; - -/// A "read-only" ring-buffer to be used together with the DMA controller which -/// writes in a circular way, "uncontrolled" to the buffer. -/// -/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field -/// to the current register value. `ndtr` describes the current position of the DMA -/// write. -/// -/// # Buffer layout -/// -/// ```text -/// Without wraparound: With wraparound: -/// -/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+ -/// | | | | | | -/// v v v v v v -/// +-----------------------------------------+ +-----------------------------------------+ -/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX| -/// +-----------------------------------------+ +-----------------------------------------+ -/// ^ ^ ^ ^ ^ ^ -/// | | | | | | -/// +- start --+ | +- end ------+ | -/// | | | | -/// +- end --------------------+ +- start ----------------+ -/// ``` -pub struct ReadableDmaRingBuffer<'a, W: Word> { - pub(crate) dma_buf: &'a mut [W], - start: usize, -} - -#[derive(Debug, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct OverrunError; - -pub trait DmaCtrl { - /// Get the NDTR register value, i.e. the space left in the underlying - /// buffer until the dma writer wraps. - fn get_remaining_transfers(&self) -> usize; - - /// Get the transfer completed counter. - /// This counter is incremented by the dma controller when NDTR is reloaded, - /// i.e. when the writing wraps. - fn get_complete_count(&self) -> usize; - - /// Reset the transfer completed counter to 0 and return the value just prior to the reset. - fn reset_complete_count(&mut self) -> usize; - - /// Set the waker for a running poll_fn - fn set_waker(&mut self, waker: &Waker); -} - -impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { - pub fn new(dma_buf: &'a mut [W]) -> Self { - Self { dma_buf, start: 0 } - } - - /// Reset the ring buffer to its initial state - pub fn clear(&mut self, dma: &mut impl DmaCtrl) { - self.start = 0; - dma.reset_complete_count(); - } - - /// The capacity of the ringbuffer - pub const fn cap(&self) -> usize { - self.dma_buf.len() - } - - /// The current position of the ringbuffer - fn pos(&self, dma: &mut impl DmaCtrl) -> usize { - self.cap() - dma.get_remaining_transfers() - } - - /// 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, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { - let mut read_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - dma.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.read(dma, &mut buffer[read_data..buffer_len]) { - Ok((len, remaining)) => { - read_data += len; - if read_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await - } - - /// 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, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { - /* - This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check - after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed - to fire in the same clock cycle that a register is read, so checking get_complete_count early does - not yield relevant information. - - Therefore, the only variable we really need to know is ndtr. If the dma has overrun by more than a full - buffer, we will do a bit more work than we have to, but algorithms should not be optimized for error - conditions. - - After we've done our work, we confirm that we haven't overrun more than a full buffer, and also that - the dma has not overrun within the data we could have copied. We check the data we could have copied - rather than the data we actually copied because it costs nothing and confirms an error condition - earlier. - */ - let end = self.pos(dma); - if self.start == end && dma.get_complete_count() == 0 { - // No elements are available in the buffer - Ok((0, self.cap())) - } else if self.start < end { - // The available, unread portion in the ring buffer DOES NOT wrap - // Copy out the elements from the dma buffer - let len = self.copy_to(buf, self.start..end); - - compiler_fence(Ordering::SeqCst); - - /* - first, check if the dma has wrapped at all if it's after end - or more than once if it's before start - - this is in a critical section to try to reduce mushy behavior. - it's not ideal but it's the best we can do - - then, get the current position of of the dma write and check - if it's inside data we could have copied - */ - let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count())); - if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 { - Err(OverrunError) - } else { - self.start = (self.start + len) % self.cap(); - - Ok((len, self.cap() - self.start)) - } - } else if self.start + buf.len() < self.cap() { - // The available, unread portion in the ring buffer DOES wrap - // The DMA writer has wrapped since we last read and is currently - // writing (or the next byte added will be) in the beginning of the ring buffer. - - // The provided read buffer is not large enough to include all elements from the tail of the dma buffer. - - // Copy out from the dma buffer - let len = self.copy_to(buf, self.start..self.cap()); - - compiler_fence(Ordering::SeqCst); - - /* - first, check if the dma has wrapped around more than once - - then, get the current position of of the dma write and check - if it's inside data we could have copied - */ - let pos = self.pos(dma); - if pos > self.start || pos < end || dma.get_complete_count() > 1 { - Err(OverrunError) - } else { - self.start = (self.start + len) % self.cap(); - - Ok((len, self.start + end)) - } - } else { - // The available, unread portion in the ring buffer DOES wrap - // The DMA writer has wrapped since we last read and is currently - // writing (or the next byte added will be) in the beginning of the ring buffer. - - // The provided read buffer is large enough to include all elements from the tail of the dma buffer, - // so the next read will not have any unread tail elements in the ring buffer. - - // Copy out from the dma buffer - let tail = self.copy_to(buf, self.start..self.cap()); - let head = self.copy_to(&mut buf[tail..], 0..end); - - compiler_fence(Ordering::SeqCst); - - /* - first, check if the dma has wrapped around more than once - - then, get the current position of of the dma write and check - if it's inside data we could have copied - */ - let pos = self.pos(dma); - if pos > self.start || pos < end || dma.reset_complete_count() > 1 { - Err(OverrunError) - } else { - self.start = head; - Ok((tail + head, self.cap() - self.start)) - } - } - } - /// Copy from the dma buffer at `data_range` into `buf` - fn copy_to(&mut self, buf: &mut [W], data_range: Range) -> usize { - // Limit the number of elements that can be copied - let length = usize::min(data_range.len(), buf.len()); - - // Copy from dma buffer into read buffer - // We need to do it like this instead of a simple copy_from_slice() because - // reading from a part of memory that may be simultaneously written to is unsafe - unsafe { - let dma_buf = self.dma_buf.as_ptr(); - - for i in 0..length { - buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize)); - } - } - - length - } -} - -pub struct WritableDmaRingBuffer<'a, W: Word> { - pub(crate) dma_buf: &'a mut [W], - end: usize, -} - -impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { - pub fn new(dma_buf: &'a mut [W]) -> Self { - Self { dma_buf, end: 0 } - } - - /// Reset the ring buffer to its initial state - pub fn clear(&mut self, dma: &mut impl DmaCtrl) { - self.end = 0; - dma.reset_complete_count(); - } - - /// The capacity of the ringbuffer - pub const fn cap(&self) -> usize { - self.dma_buf.len() - } - - /// The current position of the ringbuffer - fn pos(&self, dma: &mut impl DmaCtrl) -> usize { - 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 { - let mut written_data = 0; - let buffer_len = buffer.len(); - - poll_fn(|cx| { - dma.set_waker(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - match self.write(dma, &buffer[written_data..buffer_len]) { - Ok((len, remaining)) => { - written_data += len; - if written_data == buffer_len { - Poll::Ready(Ok(remaining)) - } else { - Poll::Pending - } - } - Err(e) => Poll::Ready(Err(e)), - } - }) - .await - } - - /// Write elements from the ring buffer - /// Return a tuple of the length written and the capacity remaining to be written in the buffer - pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), OverrunError> { - let start = self.pos(dma); - if start > self.end { - // The occupied portion in the ring buffer DOES wrap - let len = self.copy_from(buf, self.end..start); - - compiler_fence(Ordering::SeqCst); - - // Confirm that the DMA is not inside data we could have written - let (pos, complete_count) = critical_section::with(|_| (self.pos(dma), dma.get_complete_count())); - if (pos >= self.end && pos < start) || (complete_count > 0 && pos >= start) || complete_count > 1 { - Err(OverrunError) - } else { - self.end = (self.end + len) % self.cap(); - - Ok((len, self.cap() - (start - self.end))) - } - } else if start == self.end && dma.get_complete_count() == 0 { - Ok((0, 0)) - } else if start <= self.end && self.end + buf.len() < self.cap() { - // The occupied portion in the ring buffer DOES NOT wrap - // and copying elements into the buffer WILL NOT cause it to - - // Copy into the dma buffer - let len = self.copy_from(buf, self.end..self.cap()); - - compiler_fence(Ordering::SeqCst); - - // Confirm that the DMA is not inside data we could have written - let pos = self.pos(dma); - if pos > self.end || pos < start || dma.get_complete_count() > 1 { - Err(OverrunError) - } else { - self.end = (self.end + len) % self.cap(); - - Ok((len, self.cap() - (self.end - start))) - } - } else { - // The occupied portion in the ring buffer DOES NOT wrap - // and copying elements into the buffer WILL cause it to - - let tail = self.copy_from(buf, self.end..self.cap()); - let head = self.copy_from(&buf[tail..], 0..start); - - compiler_fence(Ordering::SeqCst); - - // Confirm that the DMA is not inside data we could have written - let pos = self.pos(dma); - if pos > self.end || pos < start || dma.reset_complete_count() > 1 { - Err(OverrunError) - } else { - self.end = head; - - Ok((tail + head, self.cap() - (start - self.end))) - } - } - } - /// Copy into the dma buffer at `data_range` from `buf` - fn copy_from(&mut self, buf: &[W], data_range: Range) -> usize { - // Limit the number of elements that can be copied - let length = usize::min(data_range.len(), buf.len()); - - // Copy into dma buffer from read buffer - // We need to do it like this instead of a simple copy_from_slice() because - // reading from a part of memory that may be simultaneously written to is unsafe - unsafe { - let dma_buf = self.dma_buf.as_mut_ptr(); - - for i in 0..length { - core::ptr::write_volatile(dma_buf.offset((data_range.start + i) as isize), buf[i]); - } - } - - length - } -} -#[cfg(test)] -mod tests { - use core::array; - use std::{cell, vec}; - - use super::*; - - #[allow(dead_code)] - #[derive(PartialEq, Debug)] - enum TestCircularTransferRequest { - GetCompleteCount(usize), - ResetCompleteCount(usize), - PositionRequest(usize), - } - - struct TestCircularTransfer { - len: usize, - requests: cell::RefCell>, - } - - impl DmaCtrl for TestCircularTransfer { - fn get_remaining_transfers(&self) -> usize { - match self.requests.borrow_mut().pop().unwrap() { - TestCircularTransferRequest::PositionRequest(pos) => { - let len = self.len; - - assert!(len >= pos); - - len - pos - } - _ => unreachable!(), - } - } - - fn get_complete_count(&self) -> usize { - match self.requests.borrow_mut().pop().unwrap() { - TestCircularTransferRequest::GetCompleteCount(complete_count) => complete_count, - _ => unreachable!(), - } - } - - fn reset_complete_count(&mut self) -> usize { - match self.requests.get_mut().pop().unwrap() { - TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count, - _ => unreachable!(), - } - } - - fn set_waker(&mut self, waker: &Waker) {} - } - - impl TestCircularTransfer { - pub fn new(len: usize) -> Self { - Self { - requests: cell::RefCell::new(vec![]), - len, - } - } - - pub fn setup(&self, mut requests: vec::Vec) { - requests.reverse(); - self.requests.replace(requests); - } - } - - #[test] - fn empty_and_read_not_started() { - let mut dma_buf = [0u8; 16]; - let ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - } - - #[test] - fn can_read() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!([0, 1], buf); - assert_eq!(2, ringbuf.start); - - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::PositionRequest(12), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!([2, 3], buf); - assert_eq!(4, ringbuf.start); - - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(12), - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 8]; - assert_eq!(8, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]); - assert_eq!(12, ringbuf.start); - } - - #[test] - fn can_read_with_wrap() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to close to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::PositionRequest(16), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 14]; - assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(14, ringbuf.start); - - /* - Now, read around the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::ResetCompleteCount(1), - ]); - let mut buf = [0; 6]; - assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(4, ringbuf.start); - } - - #[test] - fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to close to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::PositionRequest(16), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 14]; - assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(14, ringbuf.start); - - /* - Now, read to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::ResetCompleteCount(1), - ]); - let mut buf = [0; 2]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(0, ringbuf.start); - } - - #[test] - fn can_read_when_dma_writer_wraps_once_with_same_ndtr() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to about the middle of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 6]; - assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(6, ringbuf.start); - - /* - Now, wrap the DMA controller around - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(1), - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(1), - ]); - let mut buf = [0; 6]; - assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(12, ringbuf.start); - } - - #[test] - fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read a few bytes - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(2), - TestCircularTransferRequest::PositionRequest(2), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 6]; - assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(2, ringbuf.start); - - /* - Now, overtake the reader - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(4), - TestCircularTransferRequest::PositionRequest(6), - TestCircularTransferRequest::GetCompleteCount(1), - ]); - let mut buf = [0; 6]; - assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); - } - - #[test] - fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() { - let mut dma = TestCircularTransfer::new(16); - - let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 - let mut ringbuf = ReadableDmaRingBuffer::new(&mut dma_buf); - - assert_eq!(0, ringbuf.start); - assert_eq!(16, ringbuf.cap()); - - /* - Read to close to the end of the buffer - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(14), - TestCircularTransferRequest::PositionRequest(16), - TestCircularTransferRequest::GetCompleteCount(0), - ]); - let mut buf = [0; 14]; - assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); - assert_eq!(14, ringbuf.start); - - /* - Now, overtake the reader - */ - dma.setup(vec![ - TestCircularTransferRequest::PositionRequest(8), - TestCircularTransferRequest::PositionRequest(10), - TestCircularTransferRequest::ResetCompleteCount(2), - ]); - let mut buf = [0; 6]; - assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); - } -} diff --git a/embassy-stm32/src/dma/ringbuffer/mod.rs b/embassy-stm32/src/dma/ringbuffer/mod.rs new file mode 100644 index 000000000..44ea497fe --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/mod.rs @@ -0,0 +1,333 @@ +#![cfg_attr(gpdma, allow(unused))] + +use core::future::poll_fn; +use core::task::{Poll, Waker}; + +use crate::dma::word::Word; + +pub trait DmaCtrl { + /// Get the NDTR register value, i.e. the space left in the underlying + /// buffer until the dma writer wraps. + fn get_remaining_transfers(&self) -> usize; + + /// Reset the transfer completed counter to 0 and return the value just prior to the reset. + fn reset_complete_count(&mut self) -> usize; + + /// Set the waker for a running poll_fn + fn set_waker(&mut self, waker: &Waker); +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + Overrun, + /// the newly read DMA positions don't make sense compared to the previous + /// ones. This can usually only occur due to wrong Driver implementation, if + /// the driver author (or the user using raw metapac code) directly resets + /// the channel for instance. + DmaUnsynced, +} + +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct DmaIndex { + complete_count: usize, + pos: usize, +} + +impl DmaIndex { + fn reset(&mut self) { + self.pos = 0; + self.complete_count = 0; + } + + fn as_index(&self, cap: usize, offset: usize) -> usize { + (self.pos + offset) % cap + } + + fn dma_sync(&mut self, cap: usize, dma: &mut impl DmaCtrl) { + // Important! + // The ordering of the first two lines matters! + // If changed, the code will detect a wrong +capacity + // jump at wrap-around. + let count_diff = dma.reset_complete_count(); + let pos = cap - dma.get_remaining_transfers(); + self.pos = if pos < self.pos && count_diff == 0 { + cap - 1 + } else { + pos + }; + + self.complete_count += count_diff; + } + + fn advance(&mut self, cap: usize, steps: usize) { + let next = self.pos + steps; + self.complete_count += next / cap; + self.pos = next % cap; + } + + fn normalize(lhs: &mut DmaIndex, rhs: &mut DmaIndex) { + let min_count = lhs.complete_count.min(rhs.complete_count); + lhs.complete_count -= min_count; + rhs.complete_count -= min_count; + } + + fn diff(&self, cap: usize, rhs: &DmaIndex) -> isize { + (self.complete_count * cap + self.pos) as isize - (rhs.complete_count * cap + rhs.pos) as isize + } +} + +pub struct ReadableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + write_index: DmaIndex, + read_index: DmaIndex, +} + +impl<'a, W: Word> ReadableDmaRingBuffer<'a, W> { + /// Construct an empty buffer. + pub fn new(dma_buf: &'a mut [W]) -> Self { + Self { + dma_buf, + write_index: Default::default(), + read_index: Default::default(), + } + } + + /// Reset the ring buffer to its initial state. + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.write_index.reset(); + self.write_index.dma_sync(self.cap(), dma); + self.read_index = self.write_index; + } + + /// Get the full ringbuffer capacity. + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Get the available readable dma samples. + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.write_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.write_index, &mut self.read_index); + + let diff = self.write_index.diff(self.cap(), &self.read_index); + + if diff < 0 { + Err(Error::DmaUnsynced) + } else if diff > self.cap() as isize { + Err(Error::Overrun) + } else { + Ok(diff as usize) + } + } + + /// 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 + /// Error is returned if the portion to be read was overwritten by the DMA controller, + /// in which case the rinbuffer will automatically reset itself. + pub fn read(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { + self.read_raw(dma, buf).inspect_err(|_e| { + self.reset(dma); + }) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// Error 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, dma: &mut impl DmaCtrl, buffer: &mut [W]) -> Result { + let mut read_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.read(dma, &mut buffer[read_data..buffer_len]) { + Ok((len, remaining)) => { + read_data += len; + if read_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + fn read_raw(&mut self, dma: &mut impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), Error> { + let readable = self.len(dma)?.min(buf.len()); + for i in 0..readable { + buf[i] = self.read_buf(i); + } + let available = self.len(dma)?; + self.read_index.advance(self.cap(), readable); + Ok((readable, available - readable)) + } + + fn read_buf(&self, offset: usize) -> W { + unsafe { + core::ptr::read_volatile( + self.dma_buf + .as_ptr() + .offset(self.read_index.as_index(self.cap(), offset) as isize), + ) + } + } +} + +pub struct WritableDmaRingBuffer<'a, W: Word> { + dma_buf: &'a mut [W], + read_index: DmaIndex, + write_index: DmaIndex, +} + +impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { + /// Construct a ringbuffer filled with the given buffer data. + pub fn new(dma_buf: &'a mut [W]) -> Self { + let len = dma_buf.len(); + Self { + dma_buf, + read_index: Default::default(), + write_index: DmaIndex { + complete_count: 0, + pos: len, + }, + } + } + + /// Reset the ring buffer to its initial state. The buffer after the reset will be full. + pub fn reset(&mut self, dma: &mut impl DmaCtrl) { + dma.reset_complete_count(); + self.read_index.reset(); + self.read_index.dma_sync(self.cap(), dma); + self.write_index = self.read_index; + self.write_index.advance(self.cap(), self.cap()); + } + + /// Get the remaining writable dma samples. + pub fn len(&mut self, dma: &mut impl DmaCtrl) -> Result { + self.read_index.dma_sync(self.cap(), dma); + DmaIndex::normalize(&mut self.read_index, &mut self.write_index); + + let diff = self.write_index.diff(self.cap(), &self.read_index); + + if diff < 0 { + Err(Error::Overrun) + } else if diff > self.cap() as isize { + Err(Error::DmaUnsynced) + } else { + Ok(self.cap().saturating_sub(diff as usize)) + } + } + + /// Get the full ringbuffer capacity. + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// Append data to the ring buffer. + /// Returns a tuple of the data written and the remaining write capacity in the buffer. + /// Error is returned if the portion to be written was previously read by the DMA controller. + /// In this case, the ringbuffer will automatically reset itself, giving a full buffer worth of + /// leeway between the write index and the DMA. + pub fn write(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { + self.write_raw(dma, buf).inspect_err(|_e| { + self.reset(dma); + }) + } + + /// Write elements directly to the buffer. + /// + /// Subsequent writes will overwrite the content of the buffer, so it is not useful to call this more than once. + /// Data is aligned towards the end of the buffer. + /// + /// In case of success, returns the written length, and the empty space in front of the written block. + /// Fails if the data to write exceeds the buffer capacity. + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), Error> { + if buf.len() > self.cap() { + return Err(Error::Overrun); + } + + let start = self.cap() - buf.len(); + for (i, data) in buf.iter().enumerate() { + self.write_buf(start + i, *data) + } + let written = buf.len().min(self.cap()); + Ok((written, self.cap() - written)) + } + + /// Wait for any ring buffer write error. + pub async fn wait_write_error(&mut self, dma: &mut impl DmaCtrl) -> Result { + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.len(dma) { + Ok(_) => Poll::Pending, + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result { + let mut written_data = 0; + let buffer_len = buffer.len(); + + poll_fn(|cx| { + dma.set_waker(cx.waker()); + + match self.write(dma, &buffer[written_data..buffer_len]) { + Ok((len, remaining)) => { + written_data += len; + if written_data == buffer_len { + Poll::Ready(Ok(remaining)) + } else { + Poll::Pending + } + } + Err(e) => Poll::Ready(Err(e)), + } + }) + .await + } + + fn write_raw(&mut self, dma: &mut impl DmaCtrl, buf: &[W]) -> Result<(usize, usize), Error> { + let writable = self.len(dma)?.min(buf.len()); + for i in 0..writable { + self.write_buf(i, buf[i]); + } + let available = self.len(dma)?; + self.write_index.advance(self.cap(), writable); + Ok((writable, available - writable)) + } + + fn write_buf(&mut self, offset: usize, value: W) { + unsafe { + core::ptr::write_volatile( + self.dma_buf + .as_mut_ptr() + .offset(self.write_index.as_index(self.cap(), offset) as isize), + value, + ) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs new file mode 100644 index 000000000..6fabedb83 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/mod.rs @@ -0,0 +1,90 @@ +use std::{cell, vec}; + +use super::*; + +#[allow(unused)] +#[derive(PartialEq, Debug)] +enum TestCircularTransferRequest { + ResetCompleteCount(usize), + PositionRequest(usize), +} + +#[allow(unused)] +struct TestCircularTransfer { + len: usize, + requests: cell::RefCell>, +} + +impl DmaCtrl for TestCircularTransfer { + fn get_remaining_transfers(&self) -> usize { + match self.requests.borrow_mut().pop().unwrap() { + TestCircularTransferRequest::PositionRequest(pos) => { + let len = self.len; + + assert!(len >= pos); + + len - pos + } + _ => unreachable!(), + } + } + + fn reset_complete_count(&mut self) -> usize { + match self.requests.get_mut().pop().unwrap() { + TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count, + _ => unreachable!(), + } + } + + fn set_waker(&mut self, _waker: &Waker) {} +} + +impl TestCircularTransfer { + #[allow(unused)] + pub fn new(len: usize) -> Self { + Self { + requests: cell::RefCell::new(vec![]), + len, + } + } + + #[allow(unused)] + pub fn setup(&self, mut requests: vec::Vec) { + requests.reverse(); + self.requests.replace(requests); + } +} + +const CAP: usize = 16; + +#[test] +fn dma_index_as_index_returns_index_mod_cap_by_default() { + let index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + assert_eq!(index.as_index(CAP, 1), 1); + assert_eq!(index.as_index(CAP, 2), 2); + assert_eq!(index.as_index(CAP, 3), 3); + assert_eq!(index.as_index(CAP, 4), 4); + assert_eq!(index.as_index(CAP, CAP), 0); + assert_eq!(index.as_index(CAP, CAP + 1), 1); +} + +#[test] +fn dma_index_advancing_increases_as_index() { + let mut index = DmaIndex::default(); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 2); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 3); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 4); + index.advance(CAP, CAP - 4); + assert_eq!(index.as_index(CAP, 0), 0); + index.advance(CAP, 1); + assert_eq!(index.as_index(CAP, 0), 1); +} + +mod prop_test; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs new file mode 100644 index 000000000..661fb1728 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/mod.rs @@ -0,0 +1,50 @@ +use std::task::Waker; + +use proptest::prop_oneof; +use proptest::strategy::{self, BoxedStrategy, Strategy as _}; +use proptest_state_machine::{prop_state_machine, ReferenceStateMachine, StateMachineTest}; + +use super::*; + +const CAP: usize = 128; + +#[derive(Debug, Default)] +struct DmaMock { + pos: usize, + wraps: usize, +} + +impl DmaMock { + pub fn advance(&mut self, steps: usize) { + let next = self.pos + steps; + self.pos = next % CAP; + self.wraps += next / CAP; + } +} + +impl DmaCtrl for DmaMock { + fn get_remaining_transfers(&self) -> usize { + CAP - self.pos + } + + fn reset_complete_count(&mut self) -> usize { + core::mem::replace(&mut self.wraps, 0) + } + + fn set_waker(&mut self, _waker: &Waker) {} +} + +#[derive(Debug, Clone)] +enum Status { + Available(usize), + Failed, +} + +impl Status { + pub fn new(capacity: usize) -> Self { + Self::Available(capacity) + } +} + +mod reader; +mod writer; diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs new file mode 100644 index 000000000..4f3957a68 --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/reader.rs @@ -0,0 +1,123 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum ReaderTransition { + Write(usize), + Reset, + ReadUpTo(usize), +} + +struct ReaderSM; + +impl ReferenceStateMachine for ReaderSM { + type State = Status; + type Transition = ReaderTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(0)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(ReaderTransition::Write), + (1..50_usize).prop_map(ReaderTransition::ReadUpTo), + strategy::Just(ReaderTransition::Reset), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, ReaderTransition::Reset) => Status::Available(0), + (Status::Available(x), ReaderTransition::Write(y)) => { + if x + y > CAP { + Status::Failed + } else { + Status::Available(x + y) + } + } + (Status::Failed, ReaderTransition::Write(_)) => Status::Failed, + (Status::Available(x), ReaderTransition::ReadUpTo(y)) => Status::Available(x.saturating_sub(*y)), + (Status::Failed, ReaderTransition::ReadUpTo(_)) => Status::Available(0), + } + } +} + +struct ReaderSut { + status: Status, + buffer: *mut [u8], + producer: DmaMock, + consumer: ReadableDmaRingBuffer<'static, u8>, +} + +impl Debug for ReaderSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.producer, f) + } +} + +struct ReaderTest; + +impl StateMachineTest for ReaderTest { + type SystemUnderTest = ReaderSut; + type Reference = ReaderSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + ReaderSut { + status: ref_status.clone(), + buffer, + producer: DmaMock::default(), + consumer: ReadableDmaRingBuffer::new(unsafe { &mut *buffer }), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_state: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + ReaderTransition::Write(x) => sut.producer.advance(x), + ReaderTransition::Reset => { + sut.consumer.reset(&mut sut.producer); + } + ReaderTransition::ReadUpTo(x) => { + let status = sut.status; + let ReaderSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = consumer.read(producer, &mut buf); + match status { + Status::Available(n) => { + let readable = x.min(n); + + assert_eq!(res.unwrap().0, readable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + ReaderSut { + status: ref_state.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn reader_state_test(sequential 1..20 => ReaderTest); +} diff --git a/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs new file mode 100644 index 000000000..15433c0ee --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer/tests/prop_test/writer.rs @@ -0,0 +1,122 @@ +use core::fmt::Debug; + +use super::*; + +#[derive(Debug, Clone)] +enum WriterTransition { + Read(usize), + WriteUpTo(usize), + Reset, +} + +struct WriterSM; + +impl ReferenceStateMachine for WriterSM { + type State = Status; + type Transition = WriterTransition; + + fn init_state() -> BoxedStrategy { + strategy::Just(Status::new(CAP)).boxed() + } + + fn transitions(_state: &Self::State) -> BoxedStrategy { + prop_oneof![ + (1..50_usize).prop_map(WriterTransition::Read), + (1..50_usize).prop_map(WriterTransition::WriteUpTo), + strategy::Just(WriterTransition::Reset), + ] + .boxed() + } + + fn apply(status: Self::State, transition: &Self::Transition) -> Self::State { + match (status, transition) { + (_, WriterTransition::Reset) => Status::Available(CAP), + (Status::Available(x), WriterTransition::Read(y)) => { + if x < *y { + Status::Failed + } else { + Status::Available(x - y) + } + } + (Status::Failed, WriterTransition::Read(_)) => Status::Failed, + (Status::Available(x), WriterTransition::WriteUpTo(y)) => Status::Available((x + *y).min(CAP)), + (Status::Failed, WriterTransition::WriteUpTo(_)) => Status::Available(CAP), + } + } +} + +struct WriterSut { + status: Status, + buffer: *mut [u8], + producer: WritableDmaRingBuffer<'static, u8>, + consumer: DmaMock, +} + +impl Debug for WriterSut { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + ::fmt(&self.consumer, f) + } +} + +struct WriterTest; + +impl StateMachineTest for WriterTest { + type SystemUnderTest = WriterSut; + type Reference = WriterSM; + + fn init_test(ref_status: &::State) -> Self::SystemUnderTest { + let buffer = Box::into_raw(Box::new([0; CAP])); + WriterSut { + status: ref_status.clone(), + buffer, + producer: WritableDmaRingBuffer::new(unsafe { &mut *buffer }), + consumer: DmaMock::default(), + } + } + + fn teardown(state: Self::SystemUnderTest) { + unsafe { + let _ = Box::from_raw(state.buffer); + }; + } + + fn apply( + mut sut: Self::SystemUnderTest, + ref_status: &::State, + transition: ::Transition, + ) -> Self::SystemUnderTest { + match transition { + WriterTransition::Read(x) => sut.consumer.advance(x), + WriterTransition::Reset => { + sut.producer.reset(&mut sut.consumer); + } + WriterTransition::WriteUpTo(x) => { + let status = sut.status; + let WriterSut { + ref mut producer, + ref mut consumer, + .. + } = sut; + let mut buf = vec![0; x]; + let res = producer.write(consumer, &mut buf); + match status { + Status::Available(n) => { + let writable = x.min(CAP - n.min(CAP)); + assert_eq!(res.unwrap().0, writable); + } + Status::Failed => assert!(res.is_err()), + } + } + } + + WriterSut { + status: ref_status.clone(), + ..sut + } + } +} + +prop_state_machine! { + #[test] + fn writer_state_test(sequential 1..20 => WriterTest); +} diff --git a/embassy-stm32/src/dma/util.rs b/embassy-stm32/src/dma/util.rs index 5aaca57c9..8bf89e2fe 100644 --- a/embassy-stm32/src/dma/util.rs +++ b/embassy-stm32/src/dma/util.rs @@ -1,13 +1,12 @@ -use embassy_hal_internal::PeripheralRef; - use super::word::Word; use super::{AnyChannel, Request, Transfer, TransferOptions}; +use crate::Peri; /// Convenience wrapper, contains a channel and a request number. /// /// Commonly used in peripheral drivers that own DMA channels. pub(crate) struct ChannelAndRequest<'d> { - pub channel: PeripheralRef<'d, AnyChannel>, + pub channel: Peri<'d, AnyChannel>, pub request: Request, } @@ -18,7 +17,7 @@ impl<'d> ChannelAndRequest<'d> { buf: &'a mut [W], options: TransferOptions, ) -> Transfer<'a> { - Transfer::new_read(&mut self.channel, self.request, peri_addr, buf, options) + Transfer::new_read(self.channel.reborrow(), self.request, peri_addr, buf, options) } pub unsafe fn read_raw<'a, W: Word>( @@ -27,7 +26,7 @@ impl<'d> ChannelAndRequest<'d> { buf: *mut [W], options: TransferOptions, ) -> Transfer<'a> { - Transfer::new_read_raw(&mut self.channel, self.request, peri_addr, buf, options) + Transfer::new_read_raw(self.channel.reborrow(), self.request, peri_addr, buf, options) } pub unsafe fn write<'a, W: Word>( @@ -36,16 +35,16 @@ impl<'d> ChannelAndRequest<'d> { peri_addr: *mut W, options: TransferOptions, ) -> Transfer<'a> { - Transfer::new_write(&mut self.channel, self.request, buf, peri_addr, options) + Transfer::new_write(self.channel.reborrow(), self.request, buf, peri_addr, options) } - pub unsafe fn write_raw<'a, W: Word>( + pub unsafe fn write_raw<'a, MW: Word, PW: Word>( &'a mut self, - buf: *const [W], - peri_addr: *mut W, + buf: *const [MW], + peri_addr: *mut PW, options: TransferOptions, ) -> Transfer<'a> { - Transfer::new_write_raw(&mut self.channel, self.request, buf, peri_addr, options) + Transfer::new_write_raw(self.channel.reborrow(), self.request, buf, peri_addr, options) } #[allow(dead_code)] @@ -56,6 +55,13 @@ impl<'d> ChannelAndRequest<'d> { peri_addr: *mut W, options: TransferOptions, ) -> Transfer<'a> { - Transfer::new_write_repeated(&mut self.channel, self.request, repeated, count, peri_addr, options) + Transfer::new_write_repeated( + self.channel.reborrow(), + self.request, + repeated, + count, + peri_addr, + options, + ) } } diff --git a/embassy-stm32/src/dsihost.rs b/embassy-stm32/src/dsihost.rs index 77c3d95c3..e97ccd9d0 100644 --- a/embassy-stm32/src/dsihost.rs +++ b/embassy-stm32/src/dsihost.rs @@ -2,12 +2,12 @@ use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; //use crate::gpio::{AnyPin, SealedPin}; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; use crate::rcc::{self, RccPeripheral}; -use crate::{peripherals, Peripheral}; +use crate::{peripherals, Peri}; /// Performs a busy-wait delay for a specified number of microseconds. pub fn blocking_delay_ms(ms: u32) { @@ -69,14 +69,12 @@ impl From for u8 { /// DSIHOST driver. pub struct DsiHost<'d, T: Instance> { _peri: PhantomData<&'d mut T>, - _te: PeripheralRef<'d, AnyPin>, + _te: Peri<'d, AnyPin>, } impl<'d, T: Instance> DsiHost<'d, T> { /// Note: Full-Duplex modes are not supported at this time - pub fn new(_peri: impl Peripheral

+ 'd, te: impl Peripheral

> + 'd) -> Self { - into_ref!(te); - + pub fn new(_peri: Peri<'d, T>, te: Peri<'d, impl TePin>) -> Self { rcc::enable_and_reset::(); // Set Tearing Enable pin according to CubeMx example @@ -88,7 +86,7 @@ impl<'d, T: Instance> DsiHost<'d, T> { */ Self { _peri: PhantomData, - _te: te.map_into(), + _te: te.into(), } } @@ -412,7 +410,7 @@ trait SealedInstance: crate::rcc::SealedRccPeripheral { /// DSI instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + 'static {} +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static {} pin_trait!(TePin, Instance); diff --git a/embassy-stm32/src/dts/mod.rs b/embassy-stm32/src/dts/mod.rs new file mode 100644 index 000000000..1f39c8db5 --- /dev/null +++ b/embassy-stm32/src/dts/mod.rs @@ -0,0 +1,238 @@ +//! Digital Temperature Sensor (DTS) + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::Peri; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::InterruptExt; +use crate::peripherals::DTS; +use crate::time::Hertz; +use crate::{interrupt, pac, rcc}; + +mod tsel; +pub use tsel::TriggerSel; + +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SampleTime { + ClockCycles1 = 1, + ClockCycles2 = 2, + ClockCycles3 = 3, + ClockCycles4 = 4, + ClockCycles5 = 5, + ClockCycles6 = 6, + ClockCycles7 = 7, + ClockCycles8 = 8, + ClockCycles9 = 9, + ClockCycles10 = 10, + ClockCycles11 = 11, + ClockCycles12 = 12, + ClockCycles13 = 13, + ClockCycles14 = 14, + ClockCycles15 = 15, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// Config +pub struct Config { + /// Sample time + pub sample_time: SampleTime, + /// Trigger selection + pub trigger: TriggerSel, +} + +impl Default for Config { + fn default() -> Self { + Self { + sample_time: SampleTime::ClockCycles1, + trigger: TriggerSel::Software, + } + } +} + +/// The read-only factory calibration values used for converting a +/// measurement to a temperature. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FactoryCalibration { + /// The calibration temperature in degrees Celsius. + pub t0: u8, + /// The frequency at the calibration temperature. + pub fmt0: Hertz, + /// The ramp coefficient in Hertz per degree Celsius. + pub ramp_coeff: u16, +} + +const MAX_DTS_CLK_FREQ: Hertz = Hertz::mhz(1); + +/// Digital temperature sensor driver. +pub struct Dts<'d> { + _peri: Peri<'d, DTS>, +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +impl<'d> Dts<'d> { + /// Create a new temperature sensor driver. + pub fn new( + _peri: Peri<'d, DTS>, + _irq: impl interrupt::typelevel::Binding + 'd, + config: Config, + ) -> Self { + rcc::enable_and_reset::(); + + let prescaler = rcc::frequency::() / MAX_DTS_CLK_FREQ; + + if prescaler > 127 { + panic!("DTS PCLK frequency must be less than 127 MHz."); + } + + Self::regs().cfgr1().modify(|w| { + w.set_refclk_sel(false); + w.set_hsref_clk_div(prescaler as u8); + w.set_q_meas_opt(false); + // Software trigger + w.set_intrig_sel(0); + w.set_smp_time(config.sample_time as u8); + w.set_intrig_sel(config.trigger as u8); + w.set_start(true); + w.set_en(true); + }); + + interrupt::DTS.unpend(); + unsafe { interrupt::DTS.enable() }; + + Self { _peri } + } + + /// Reconfigure the driver. + pub fn set_config(&mut self, config: &Config) { + Self::regs().cfgr1().modify(|w| { + w.set_smp_time(config.sample_time as u8); + w.set_intrig_sel(config.trigger as u8); + }); + } + + /// Get the read-only factory calibration values used for converting a + /// measurement to a temperature. + pub fn factory_calibration() -> FactoryCalibration { + let t0valr1 = Self::regs().t0valr1().read(); + let t0 = match t0valr1.t0() { + 0 => 30, + 1 => 130, + _ => unimplemented!(), + }; + let fmt0 = Hertz::hz(t0valr1.fmt0() as u32 * 100); + + let ramp_coeff = Self::regs().rampvalr().read().ramp_coeff(); + + FactoryCalibration { t0, fmt0, ramp_coeff } + } + + /// Perform an asynchronous temperature measurement. The returned future can + /// be awaited to obtain the measurement. + /// + /// The future returned waits for the next measurement to complete. + /// + /// # Example + /// + /// ```no_run + /// use embassy_stm32::{bind_interrupts, dts}; + /// use embassy_stm32::dts::Dts; + /// + /// bind_interrupts!(struct Irqs { + /// DTS => temp::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_stm32::Peripherals = todo!(); + /// let mut dts = Dts::new(p.DTS, Irqs, Default::default()); + /// let v: u16 = dts.read().await; + /// # }; + /// ``` + pub async fn read(&mut self) -> u16 { + let r = Self::regs(); + + r.itenr().modify(|w| w.set_iteen(true)); + + poll_fn(|cx| { + WAKER.register(cx.waker()); + if r.itenr().read().iteen() { + Poll::Pending + } else { + Poll::Ready(r.dr().read().mfreq()) + } + }) + .await + } + + /// Returns the last measurement made, if any. + /// + /// There is no guarantee that the measurement is recent or that a + /// measurement has ever completed. + pub fn read_immediate(&mut self) -> u16 { + Self::regs().dr().read().mfreq() + } + + fn regs() -> pac::dts::Dts { + pac::DTS + } +} + +impl<'d> Drop for Dts<'d> { + fn drop(&mut self) { + Self::regs().cfgr1().modify(|w| w.set_en(false)); + rcc::disable::(); + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = pac::DTS; + let (sr, itenr) = (r.sr().read(), r.itenr().read()); + + if (itenr.iteen() && sr.itef()) || (itenr.aiteen() && sr.aitef()) { + r.itenr().modify(|w| { + w.set_iteen(false); + w.set_aiteen(false); + }); + r.icifr().modify(|w| { + w.set_citef(true); + w.set_caitef(true); + }); + } else if (itenr.itlen() && sr.itlf()) || (itenr.aitlen() && sr.aitlf()) { + r.itenr().modify(|w| { + w.set_itlen(false); + w.set_aitlen(false); + }); + r.icifr().modify(|w| { + w.set_citlf(true); + w.set_caitlf(true); + }); + } else if (itenr.ithen() && sr.ithf()) || (itenr.aithen() && sr.aithf()) { + r.itenr().modify(|w| { + w.set_ithen(false); + w.set_aithen(false); + }); + r.icifr().modify(|w| { + w.set_cithf(true); + w.set_caithf(true); + }); + } else { + return; + } + + compiler_fence(Ordering::SeqCst); + WAKER.wake(); + } +} diff --git a/embassy-stm32/src/dts/tsel.rs b/embassy-stm32/src/dts/tsel.rs new file mode 100644 index 000000000..99eab6dd8 --- /dev/null +++ b/embassy-stm32/src/dts/tsel.rs @@ -0,0 +1,51 @@ +/// Trigger selection for H5 +#[cfg(stm32h5)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + /// Software triggering. Performs continuous measurements. + Software = 0, + /// LPTIM1 CH1 + Lptim1 = 1, + /// LPTIM2 CH1 + Lptim2 = 2, + /// LPTIM3 CH1 + #[cfg(not(stm32h503))] + Lptim3 = 3, + /// EXTI13 + Exti13 = 4, +} + +/// Trigger selection for H7, except for H7R and H7S +#[cfg(stm32h7)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + /// Software triggering. Performs continuous measurements. + Software = 0, + /// LPTIM1 OUT + Lptim1 = 1, + /// LPTIM2 OUT + Lptim2 = 2, + /// LPTIM3 OUT + Lptim3 = 3, + /// EXTI13 + Exti13 = 4, +} + +/// Trigger selection for H7R and H7S +#[cfg(stm32h7rs)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + /// Software triggering. Performs continuous measurements. + Software = 0, + /// LPTIM4 OUT + Lptim4 = 1, + /// LPTIM2 CH1 + Lptim2 = 2, + /// LPTIM3 CH1 + Lptim3 = 3, + /// EXTI13 + Exti13 = 4, +} diff --git a/embassy-stm32/src/eth/generic_smi.rs b/embassy-stm32/src/eth/generic_phy.rs similarity index 68% rename from embassy-stm32/src/eth/generic_smi.rs rename to embassy-stm32/src/eth/generic_phy.rs index 3b43051f4..774beef80 100644 --- a/embassy-stm32/src/eth/generic_smi.rs +++ b/embassy-stm32/src/eth/generic_phy.rs @@ -7,7 +7,7 @@ use embassy_time::{Duration, Timer}; #[cfg(feature = "time")] use futures_util::FutureExt; -use super::{StationManagement, PHY}; +use super::{Phy, StationManagement}; #[allow(dead_code)] mod phy_consts { @@ -43,25 +43,71 @@ mod phy_consts { use self::phy_consts::*; /// Generic SMI Ethernet PHY implementation -pub struct GenericSMI { +pub struct GenericPhy { phy_addr: u8, #[cfg(feature = "time")] poll_interval: Duration, } -impl GenericSMI { +impl GenericPhy { /// Construct the PHY. It assumes the address `phy_addr` in the SMI communication + /// + /// # Panics + /// `phy_addr` must be in range `0..32` pub fn new(phy_addr: u8) -> Self { + assert!(phy_addr < 32); Self { phy_addr, #[cfg(feature = "time")] poll_interval: Duration::from_millis(500), } } + + /// Construct the PHY. Try to probe all addresses from 0 to 31 during initialization + /// + /// # Panics + /// Initialization panics if PHY didn't respond on any address + pub fn new_auto() -> Self { + Self { + phy_addr: 0xFF, + #[cfg(feature = "time")] + poll_interval: Duration::from_millis(500), + } + } } -unsafe impl PHY for GenericSMI { +// TODO: Factor out to shared functionality +fn blocking_delay_us(us: u32) { + #[cfg(feature = "time")] + embassy_time::block_for(Duration::from_micros(us as u64)); + #[cfg(not(feature = "time"))] + { + let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; + let us = us as u64; + let cycles = freq * us / 1_000_000; + cortex_m::asm::delay(cycles as u32); + } +} + +impl Phy for GenericPhy { fn phy_reset(&mut self, sm: &mut S) { + // Detect SMI address + if self.phy_addr == 0xFF { + for addr in 0..32 { + sm.smi_write(addr, PHY_REG_BCR, PHY_REG_BCR_RESET); + for _ in 0..10 { + if sm.smi_read(addr, PHY_REG_BCR) & PHY_REG_BCR_RESET != PHY_REG_BCR_RESET { + trace!("Found ETH PHY on address {}", addr); + self.phy_addr = addr; + return; + } + // Give PHY a total of 100ms to respond + blocking_delay_us(10000); + } + } + panic!("PHY did not respond"); + } + sm.smi_write(self.phy_addr, PHY_REG_BCR, PHY_REG_BCR_RESET); while sm.smi_read(self.phy_addr, PHY_REG_BCR) & PHY_REG_BCR_RESET == PHY_REG_BCR_RESET {} } @@ -102,7 +148,7 @@ unsafe impl PHY for GenericSMI { } /// Public functions for the PHY -impl GenericSMI { +impl GenericPhy { /// Set the SMI polling interval. #[cfg(feature = "time")] pub fn set_poll_interval(&mut self, poll_interval: Duration) { diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index bfe8a60d6..97d7b4347 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -4,15 +4,17 @@ #[cfg_attr(any(eth_v1a, eth_v1b, eth_v1c), path = "v1/mod.rs")] #[cfg_attr(eth_v2, path = "v2/mod.rs")] mod _version; -pub mod generic_smi; +mod generic_phy; use core::mem::MaybeUninit; use core::task::Context; +use embassy_hal_internal::PeripheralType; use embassy_net_driver::{Capabilities, HardwareAddress, LinkState}; use embassy_sync::waitqueue::AtomicWaker; pub use self::_version::{InterruptHandler, *}; +pub use self::generic_phy::*; use crate::rcc::RccPeripheral; #[allow(unused)] @@ -42,11 +44,9 @@ pub struct PacketQueue { impl PacketQueue { /// Create a new packet queue. pub const fn new() -> Self { - const NEW_TDES: TDes = TDes::new(); - const NEW_RDES: RDes = RDes::new(); Self { - tx_desc: [NEW_TDES; TX], - rx_desc: [NEW_RDES; RX], + tx_desc: [const { TDes::new() }; TX], + rx_desc: [const { RDes::new() }; RX], tx_buf: [Packet([0; TX_BUFFER_SIZE]); TX], rx_buf: [Packet([0; RX_BUFFER_SIZE]); RX], } @@ -73,9 +73,15 @@ impl PacketQueue { static WAKER: AtomicWaker = AtomicWaker::new(); -impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P> { - type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; - type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; +impl<'d, T: Instance, P: Phy> embassy_net_driver::Driver for Ethernet<'d, T, P> { + type RxToken<'a> + = RxToken<'a, 'd> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, 'd> + where + Self: 'a; fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { WAKER.register(cx.waker()); @@ -152,23 +158,15 @@ impl<'a, 'd> embassy_net_driver::TxToken for TxToken<'a, 'd> { } /// Station Management Interface (SMI) on an ethernet PHY -/// -/// # Safety -/// -/// The methods cannot move out of self -pub unsafe trait StationManagement { +pub trait StationManagement { /// Read a register over SMI. fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16; /// Write a register over SMI. fn smi_write(&mut self, phy_addr: u8, reg: u8, val: u16); } -/// Traits for an Ethernet PHY -/// -/// # Safety -/// -/// The methods cannot move S -pub unsafe trait PHY { +/// Trait for an Ethernet PHY +pub trait Phy { /// Reset PHY and wait for it to come out of reset. fn phy_reset(&mut self, sm: &mut S); /// PHY initialisation. @@ -177,13 +175,32 @@ pub unsafe trait PHY { fn poll_link(&mut self, sm: &mut S, cx: &mut Context) -> bool; } +impl<'d, T: Instance, P: Phy> Ethernet<'d, T, P> { + /// Directly expose the SMI interface used by the Ethernet driver. + /// + /// This can be used to for example configure special PHY registers for compliance testing. + pub fn station_management(&mut self) -> &mut impl StationManagement { + &mut self.station_management + } + + /// Access the user-supplied `Phy`. + pub fn phy(&self) -> &P { + &self.phy + } + + /// Mutably access the user-supplied `Phy`. + pub fn phy_mut(&mut self) -> &mut P { + &mut self.phy + } +} + trait SealedInstance { fn regs() -> crate::pac::eth::Eth; } /// Ethernet instance. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + Send + 'static {} +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + Send + 'static {} impl SealedInstance for crate::peripherals::ETH { fn regs() -> crate::pac::eth::Eth { diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs index cce75ece7..01e321bce 100644 --- a/embassy-stm32/src/eth/v1/mod.rs +++ b/embassy-stm32/src/eth/v1/mod.rs @@ -6,7 +6,7 @@ mod tx_desc; use core::marker::PhantomData; use core::sync::atomic::{fence, Ordering}; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::Peri; use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; pub(crate) use self::rx_desc::{RDes, RDesRing}; @@ -15,6 +15,7 @@ use super::*; #[cfg(eth_v1a)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; +use crate::interrupt; use crate::interrupt::InterruptExt; #[cfg(eth_v1a)] use crate::pac::AFIO; @@ -22,7 +23,6 @@ use crate::pac::AFIO; use crate::pac::SYSCFG; use crate::pac::{ETH, RCC}; use crate::rcc::SealedRccPeripheral; -use crate::{interrupt, Peripheral}; /// Interrupt handler. pub struct InterruptHandler {} @@ -46,17 +46,23 @@ impl interrupt::typelevel::Handler for InterruptHandl } /// Ethernet driver. -pub struct Ethernet<'d, T: Instance, P: PHY> { - _peri: PeripheralRef<'d, T>, +pub struct Ethernet<'d, T: Instance, P: Phy> { + _peri: Peri<'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, pub(crate) mac_addr: [u8; 6], } +/// Pins of ethernet driver. +enum Pins<'d> { + Rmii([Peri<'d, AnyPin>; 9]), + Mii([Peri<'d, AnyPin>; 14]), +} + #[cfg(eth_v1a)] macro_rules! config_in_pins { ($($pin:ident),*) => { @@ -91,26 +97,24 @@ macro_rules! config_pins { }; } -impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { +impl<'d, T: Instance, P: Phy> Ethernet<'d, T, P> { /// safety: the returned instance is not leak-safe pub fn new( queue: &'d mut PacketQueue, - peri: impl Peripheral

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

> + 'd, - mdio: impl Peripheral

> + 'd, - mdc: impl Peripheral

> + 'd, - crs: impl Peripheral

> + 'd, - rx_d0: impl Peripheral

> + 'd, - rx_d1: impl Peripheral

> + 'd, - tx_d0: impl Peripheral

> + 'd, - tx_d1: impl Peripheral

> + 'd, - tx_en: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + irq: impl interrupt::typelevel::Binding + 'd, + ref_clk: Peri<'d, impl RefClkPin>, + mdio: Peri<'d, impl MDIOPin>, + mdc: Peri<'d, impl MDCPin>, + crs: Peri<'d, impl CRSPin>, + rx_d0: Peri<'d, impl RXD0Pin>, + rx_d1: Peri<'d, impl RXD1Pin>, + tx_d0: Peri<'d, impl TXD0Pin>, + tx_d1: Peri<'d, impl TXD1Pin>, + tx_en: Peri<'d, impl TXEnPin>, 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(eth_v1a)] critical_section::with(|_| { @@ -148,6 +152,29 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { #[cfg(any(eth_v1b, eth_v1c))] config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + let pins = Pins::Rmii([ + ref_clk.into(), + mdio.into(), + mdc.into(), + crs.into(), + rx_d0.into(), + rx_d1.into(), + tx_d0.into(), + tx_d1.into(), + tx_en.into(), + ]); + + Self::new_inner(queue, peri, irq, pins, phy, mac_addr) + } + + fn new_inner( + queue: &'d mut PacketQueue, + peri: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding + 'd, + pins: Pins<'d>, + phy: P, + mac_addr: [u8; 6], + ) -> Self { let dma = T::regs().ethernet_dma(); let mac = T::regs().ethernet_mac(); @@ -159,8 +186,13 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { w.set_ifg(Ifg::IFG96); // inter frame gap 96 bit times w.set_apcs(Apcs::STRIP); // automatic padding and crc stripping w.set_fes(Fes::FES100); // fast ethernet speed - w.set_dm(Dm::FULLDUPLEX); // full duplex - // TODO: Carrier sense ? ECRSFD + w.set_dm(Dm::FULL_DUPLEX); // full duplex + // TODO: Carrier sense ? ECRSFD + }); + + // Set the mac to pass all multicast packets + mac.macffr().modify(|w| { + w.set_pam(true); }); // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, @@ -181,8 +213,8 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { // Transfer and Forward, Receive and Forward dma.dmaomr().modify(|w| { - w.set_tsf(Tsf::STOREFORWARD); - w.set_rsf(Rsf::STOREFORWARD); + w.set_tsf(Tsf::STORE_FORWARD); + w.set_rsf(Rsf::STORE_FORWARD); }); dma.dmabmr().modify(|w| { @@ -207,18 +239,6 @@ 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, pins, @@ -264,15 +284,96 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { this } + + /// Create a new MII ethernet driver using 14 pins. + pub fn new_mii( + queue: &'d mut PacketQueue, + peri: Peri<'d, T>, + irq: impl interrupt::typelevel::Binding + 'd, + rx_clk: Peri<'d, impl RXClkPin>, + tx_clk: Peri<'d, impl TXClkPin>, + mdio: Peri<'d, impl MDIOPin>, + mdc: Peri<'d, impl MDCPin>, + rxdv: Peri<'d, impl RXDVPin>, + rx_d0: Peri<'d, impl RXD0Pin>, + rx_d1: Peri<'d, impl RXD1Pin>, + rx_d2: Peri<'d, impl RXD2Pin>, + rx_d3: Peri<'d, impl RXD3Pin>, + tx_d0: Peri<'d, impl TXD0Pin>, + tx_d1: Peri<'d, impl TXD1Pin>, + tx_d2: Peri<'d, impl TXD2Pin>, + tx_d3: Peri<'d, impl TXD3Pin>, + tx_en: Peri<'d, impl TXEnPin>, + phy: P, + mac_addr: [u8; 6], + ) -> Self { + // TODO: Handle optional signals like CRS, MII_COL, RX_ER? + + // Enable the necessary Clocks + #[cfg(eth_v1a)] + critical_section::with(|_| { + RCC.apb2enr().modify(|w| w.set_afioen(true)); + + // Select MII (Media Independent Interface) + // Must be done prior to enabling peripheral clock + AFIO.mapr().modify(|w| w.set_mii_rmii_sel(false)); + + RCC.ahbenr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + }); + + #[cfg(any(eth_v1b, eth_v1c))] + critical_section::with(|_| { + RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + + // MII (Media Independent Interface) + SYSCFG.pmc().modify(|w| w.set_mii_rmii_sel(false)); + }); + + #[cfg(eth_v1a)] + { + config_in_pins!(rx_clk, tx_clk, rx_d0, rx_d1, rx_d2, rx_d3, rxdv); + config_af_pins!(mdio, mdc, tx_d0, tx_d1, tx_d2, tx_d3, tx_en); + } + + #[cfg(any(eth_v1b, eth_v1c))] + 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.into(), + tx_clk.into(), + mdio.into(), + mdc.into(), + rxdv.into(), + rx_d0.into(), + rx_d1.into(), + rx_d2.into(), + rx_d3.into(), + tx_d0.into(), + tx_d1.into(), + tx_d2.into(), + tx_d3.into(), + tx_en.into(), + ]); + + Self::new_inner(queue, peri, irq, pins, phy, mac_addr) + } } /// Ethernet station management interface. -pub struct EthernetStationManagement { +pub(crate) struct EthernetStationManagement { peri: PhantomData, clock_range: Cr, } -unsafe impl StationManagement for EthernetStationManagement { +impl StationManagement for EthernetStationManagement { fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16 { let mac = T::regs().ethernet_mac(); @@ -302,7 +403,7 @@ unsafe impl StationManagement for EthernetStationManagement { } } -impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { +impl<'d, T: Instance, P: Phy> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { let dma = T::regs().ethernet_dma(); let mac = T::regs().ethernet_mac(); @@ -319,7 +420,10 @@ impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { dma.dmaomr().modify(|w| w.set_sr(DmaomrSr::STOPPED)); 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/eth/v1/rx_desc.rs b/embassy-stm32/src/eth/v1/rx_desc.rs index 668378bea..2a46c1895 100644 --- a/embassy-stm32/src/eth/v1/rx_desc.rs +++ b/embassy-stm32/src/eth/v1/rx_desc.rs @@ -168,15 +168,15 @@ impl<'a> RDesRing<'a> { // Reset or Stop Receive Command issued Rps::STOPPED => RunningState::Stopped, // Fetching receive transfer descriptor - Rps::RUNNINGFETCHING => RunningState::Running, + Rps::RUNNING_FETCHING => RunningState::Running, // Waiting for receive packet - Rps::RUNNINGWAITING => RunningState::Running, + Rps::RUNNING_WAITING => RunningState::Running, // Receive descriptor unavailable Rps::SUSPENDED => RunningState::Stopped, // Closing receive descriptor Rps::_RESERVED_5 => RunningState::Running, // Transferring the receive packet data from receive buffer to host memory - Rps::RUNNINGWRITING => RunningState::Running, + Rps::RUNNING_WRITING => RunningState::Running, _ => RunningState::Unknown, } } diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs index b26f08cd9..034c5dd88 100644 --- a/embassy-stm32/src/eth/v2/mod.rs +++ b/embassy-stm32/src/eth/v2/mod.rs @@ -3,16 +3,16 @@ mod descriptors; use core::marker::PhantomData; use core::sync::atomic::{fence, Ordering}; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::Peri; use stm32_metapac::syscfg::vals::EthSelPhy; pub(crate) use self::descriptors::{RDes, RDesRing, TDes, TDesRing}; use super::*; use crate::gpio::{AfType, AnyPin, OutputType, SealedPin as _, Speed}; +use crate::interrupt; use crate::interrupt::InterruptExt; use crate::pac::ETH; use crate::rcc::SealedRccPeripheral; -use crate::{interrupt, Peripheral}; /// Interrupt handler. pub struct InterruptHandler {} @@ -36,8 +36,8 @@ impl interrupt::typelevel::Handler for InterruptHandl } /// Ethernet driver. -pub struct Ethernet<'d, T: Instance, P: PHY> { - _peri: PeripheralRef<'d, T>, +pub struct Ethernet<'d, T: Instance, P: Phy> { + _peri: Peri<'d, T>, pub(crate) tx: TDesRing<'d>, pub(crate) rx: RDesRing<'d>, pins: Pins<'d>, @@ -48,8 +48,8 @@ pub struct Ethernet<'d, T: Instance, P: PHY> { /// Pins of ethernet driver. enum Pins<'d> { - Rmii([PeripheralRef<'d, AnyPin>; 9]), - Mii([PeripheralRef<'d, AnyPin>; 14]), + Rmii([Peri<'d, AnyPin>; 9]), + Mii([Peri<'d, AnyPin>; 14]), } macro_rules! config_pins { @@ -63,21 +63,21 @@ macro_rules! config_pins { }; } -impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { +impl<'d, T: Instance, P: Phy> Ethernet<'d, T, P> { /// Create a new RMII ethernet driver using 9 pins. pub fn new( queue: &'d mut PacketQueue, - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, irq: impl interrupt::typelevel::Binding + 'd, - ref_clk: impl Peripheral

> + 'd, - mdio: impl Peripheral

> + 'd, - mdc: impl Peripheral

> + 'd, - crs: impl Peripheral

> + 'd, - rx_d0: impl Peripheral

> + 'd, - rx_d1: impl Peripheral

> + 'd, - tx_d0: impl Peripheral

> + 'd, - tx_d1: impl Peripheral

> + 'd, - tx_en: impl Peripheral

> + 'd, + ref_clk: Peri<'d, impl RefClkPin>, + mdio: Peri<'d, impl MDIOPin>, + mdc: Peri<'d, impl MDCPin>, + crs: Peri<'d, impl CRSPin>, + rx_d0: Peri<'d, impl RXD0Pin>, + rx_d1: Peri<'d, impl RXD1Pin>, + tx_d0: Peri<'d, impl TXD0Pin>, + tx_d1: Peri<'d, impl TXD1Pin>, + tx_en: Peri<'d, impl TXEnPin>, phy: P, mac_addr: [u8; 6], ) -> Self { @@ -92,19 +92,18 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { crate::pac::SYSCFG.pmcr().modify(|w| w.set_eth_sel_phy(EthSelPhy::RMII)); }); - 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(), + ref_clk.into(), + mdio.into(), + mdc.into(), + crs.into(), + rx_d0.into(), + rx_d1.into(), + tx_d0.into(), + tx_d1.into(), + tx_en.into(), ]); Self::new_inner(queue, peri, irq, pins, phy, mac_addr) @@ -113,22 +112,22 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { /// Create a new MII ethernet driver using 14 pins. pub fn new_mii( queue: &'d mut PacketQueue, - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, irq: impl interrupt::typelevel::Binding + 'd, - rx_clk: impl Peripheral

> + 'd, - tx_clk: impl Peripheral

> + 'd, - mdio: impl Peripheral

> + 'd, - mdc: impl Peripheral

> + 'd, - rxdv: impl Peripheral

> + 'd, - rx_d0: impl Peripheral

> + 'd, - rx_d1: impl Peripheral

> + 'd, - rx_d2: impl Peripheral

> + 'd, - rx_d3: impl Peripheral

> + 'd, - tx_d0: impl Peripheral

> + 'd, - tx_d1: impl Peripheral

> + 'd, - tx_d2: impl Peripheral

> + 'd, - tx_d3: impl Peripheral

> + 'd, - tx_en: impl Peripheral

> + 'd, + rx_clk: Peri<'d, impl RXClkPin>, + tx_clk: Peri<'d, impl TXClkPin>, + mdio: Peri<'d, impl MDIOPin>, + mdc: Peri<'d, impl MDCPin>, + rxdv: Peri<'d, impl RXDVPin>, + rx_d0: Peri<'d, impl RXD0Pin>, + rx_d1: Peri<'d, impl RXD1Pin>, + rx_d2: Peri<'d, impl RXD2Pin>, + rx_d3: Peri<'d, impl RXD3Pin>, + tx_d0: Peri<'d, impl TXD0Pin>, + tx_d1: Peri<'d, impl TXD1Pin>, + tx_d2: Peri<'d, impl TXD2Pin>, + tx_d3: Peri<'d, impl TXD3Pin>, + tx_en: Peri<'d, impl TXEnPin>, phy: P, mac_addr: [u8; 6], ) -> Self { @@ -145,24 +144,23 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { .modify(|w| w.set_eth_sel_phy(EthSelPhy::MII_GMII)); }); - 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(), + rx_clk.into(), + tx_clk.into(), + mdio.into(), + mdc.into(), + rxdv.into(), + rx_d0.into(), + rx_d1.into(), + rx_d2.into(), + rx_d3.into(), + tx_d0.into(), + tx_d1.into(), + tx_d2.into(), + tx_d3.into(), + tx_en.into(), ]); Self::new_inner(queue, peri, irq, pins, phy, mac_addr) @@ -170,7 +168,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { fn new_inner( queue: &'d mut PacketQueue, - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding + 'd, pins: Pins<'d>, phy: P, @@ -192,6 +190,9 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { // TODO: Carrier sense ? ECRSFD }); + // Disable multicast filter + mac.macpfr().modify(|w| w.set_pm(true)); + // Note: Writing to LR triggers synchronisation of both LR and HR into the MAC core, // so the LR write must happen after the HR write. mac.maca0hr() @@ -251,7 +252,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { }; let mut this = Self { - _peri: peri.into_ref(), + _peri: peri, tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), pins, @@ -301,7 +302,7 @@ pub struct EthernetStationManagement { clock_range: u8, } -unsafe impl StationManagement for EthernetStationManagement { +impl StationManagement for EthernetStationManagement { fn smi_read(&mut self, phy_addr: u8, reg: u8) -> u16 { let mac = T::regs().ethernet_mac(); @@ -331,7 +332,7 @@ unsafe impl StationManagement for EthernetStationManagement { } } -impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { +impl<'d, T: Instance, P: Phy> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { let dma = T::regs().ethernet_dma(); let mac = T::regs().ethernet_mac(); diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index 224d51b84..9fce78f95 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -5,21 +5,25 @@ use core::marker::PhantomData; use core::pin::Pin; use core::task::{Context, Poll}; -use embassy_hal_internal::{impl_peripheral, into_ref}; +use embassy_hal_internal::{impl_peripheral, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; 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}; +use crate::{interrupt, pac, peripherals, Peri}; const EXTI_COUNT: usize = 16; -const NEW_AW: AtomicWaker = AtomicWaker::new(); -static EXTI_WAKERS: [AtomicWaker; EXTI_COUNT] = [NEW_AW; EXTI_COUNT]; +static EXTI_WAKERS: [AtomicWaker; EXTI_COUNT] = [const { AtomicWaker::new() }; EXTI_COUNT]; -#[cfg(exti_w)] +#[cfg(all(exti_w, feature = "_core-cm0p"))] fn cpu_regs() -> pac::exti::Cpu { - EXTI.cpu(crate::pac::CORE_INDEX) + EXTI.cpu(1) +} + +#[cfg(all(exti_w, not(feature = "_core-cm0p")))] +fn cpu_regs() -> pac::exti::Cpu { + EXTI.cpu(0) } #[cfg(not(exti_w))] @@ -41,9 +45,6 @@ fn exticr_regs() -> pac::afio::Afio { } unsafe fn on_irq() { - #[cfg(feature = "low-power")] - crate::low_power::on_wakeup_irq(); - #[cfg(not(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50)))] let bits = EXTI.pr(0).read().0; #[cfg(any(exti_c0, exti_g0, exti_u0, exti_l5, exti_u5, exti_h5, exti_h50))] @@ -68,6 +69,9 @@ unsafe fn on_irq() { EXTI.rpr(0).write_value(Lines(bits)); EXTI.fpr(0).write_value(Lines(bits)); } + + #[cfg(feature = "low-power")] + crate::low_power::on_wakeup_irq(); } struct BitIter(u32); @@ -101,13 +105,7 @@ impl<'d> Unpin for ExtiInput<'d> {} impl<'d> ExtiInput<'d> { /// Create an EXTI input. - pub fn new( - pin: impl Peripheral

+ 'd, - ch: impl Peripheral

+ 'd, - pull: Pull, - ) -> Self { - into_ref!(pin, ch); - + pub fn new(pin: Peri<'d, T>, ch: Peri<'d, T::ExtiChannel>, pull: Pull) -> Self { // Needed if using AnyPin+AnyChannel. assert_eq!(pin.pin(), ch.number()); @@ -334,23 +332,12 @@ trait SealedChannel {} /// EXTI channel trait. #[allow(private_bounds)] -pub trait Channel: SealedChannel + Sized { +pub trait Channel: PeripheralType + SealedChannel + Sized { /// Get the EXTI channel number. fn number(&self) -> u8; - - /// Type-erase (degrade) this channel into an `AnyChannel`. - /// - /// This converts EXTI channel singletons (`EXTI0`, `EXTI1`, ...), which - /// are all different types, into the same type. It is useful for - /// creating arrays of channels, or avoiding generics. - fn degrade(self) -> AnyChannel { - AnyChannel { - number: self.number() as u8, - } - } } -/// Type-erased (degraded) EXTI channel. +/// Type-erased EXTI channel. /// /// This represents ownership over any EXTI channel, known at runtime. pub struct AnyChannel { @@ -373,6 +360,14 @@ macro_rules! impl_exti { $number } } + + impl From for AnyChannel { + fn from(val: peripherals::$type) -> Self { + Self { + number: val.number() as u8, + } + } + } }; } diff --git a/embassy-stm32/src/flash/asynch.rs b/embassy-stm32/src/flash/asynch.rs index 9468ac632..006dcddeb 100644 --- a/embassy-stm32/src/flash/asynch.rs +++ b/embassy-stm32/src/flash/asynch.rs @@ -2,28 +2,25 @@ use core::marker::PhantomData; use core::sync::atomic::{fence, Ordering}; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::into_ref; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; use super::{ - blocking_read, ensure_sector_aligned, family, get_sector, Async, Error, Flash, FlashLayout, FLASH_BASE, FLASH_SIZE, - WRITE_SIZE, + blocking_read, ensure_sector_aligned, family, get_flash_regions, get_sector, Async, Error, Flash, FlashLayout, + FLASH_BASE, FLASH_SIZE, WRITE_SIZE, }; use crate::interrupt::InterruptExt; use crate::peripherals::FLASH; -use crate::{interrupt, Peripheral}; +use crate::{interrupt, Peri}; pub(super) static REGION_ACCESS: Mutex = Mutex::new(()); impl<'d> Flash<'d, Async> { /// Create a new flash driver with async capabilities. pub fn new( - p: impl Peripheral

+ 'd, + p: Peri<'d, FLASH>, _irq: impl interrupt::typelevel::Binding + 'd, ) -> Self { - into_ref!(p); - crate::interrupt::FLASH.unpend(); unsafe { crate::interrupt::FLASH.enable() }; @@ -37,7 +34,6 @@ impl<'d> Flash<'d, Async> { /// /// See module-level documentation for details on how memory regions work. pub fn into_regions(self) -> FlashLayout<'d, Async> { - assert!(family::is_default_layout()); FlashLayout::new(self.inner) } @@ -126,7 +122,7 @@ pub(super) async unsafe fn write_chunked(base: u32, size: u32, offset: u32, byte pub(super) async unsafe fn erase_sectored(base: u32, from: u32, to: u32) -> Result<(), Error> { let start_address = base + from; let end_address = base + to; - let regions = family::get_flash_regions(); + let regions = get_flash_regions(); ensure_sector_aligned(start_address, end_address, regions)?; diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs index 8ec4bb2a1..10023e637 100644 --- a/embassy-stm32/src/flash/common.rs +++ b/embassy-stm32/src/flash/common.rs @@ -2,26 +2,27 @@ use core::marker::PhantomData; use core::sync::atomic::{fence, Ordering}; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, PeripheralRef}; -use stm32_metapac::FLASH_BASE; use super::{ - family, Async, Blocking, Error, FlashBank, FlashLayout, FlashRegion, FlashSector, FLASH_SIZE, MAX_ERASE_SIZE, - READ_SIZE, WRITE_SIZE, + family, get_flash_regions, Async, Blocking, Error, FlashBank, FlashLayout, FlashRegion, FlashSector, FLASH_SIZE, + MAX_ERASE_SIZE, READ_SIZE, WRITE_SIZE, }; +use crate::Peri; +use crate::_generated::FLASH_BASE; use crate::peripherals::FLASH; -use crate::Peripheral; /// Internal flash memory driver. pub struct Flash<'d, MODE = Async> { - pub(crate) inner: PeripheralRef<'d, FLASH>, + pub(crate) inner: Peri<'d, FLASH>, pub(crate) _mode: PhantomData, } impl<'d> Flash<'d, Blocking> { /// Create a new flash driver, usable in blocking mode. - pub fn new_blocking(p: impl Peripheral

+ 'd) -> Self { - into_ref!(p); + pub fn new_blocking(p: Peri<'d, FLASH>) -> Self { + #[cfg(bank_setup_configurable)] + // Check if the configuration matches the embassy setup + super::check_bank_setup(); Self { inner: p, @@ -35,7 +36,6 @@ impl<'d, MODE> Flash<'d, MODE> { /// /// See module-level documentation for details on how memory regions work. pub fn into_blocking_regions(self) -> FlashLayout<'d, Blocking> { - assert!(family::is_default_layout()); FlashLayout::new(self.inner) } @@ -140,7 +140,7 @@ pub(super) unsafe fn blocking_erase( ) -> Result<(), Error> { let start_address = base + from; let end_address = base + to; - let regions = family::get_flash_regions(); + let regions = get_flash_regions(); ensure_sector_aligned(start_address, end_address, regions)?; diff --git a/embassy-stm32/src/flash/eeprom.rs b/embassy-stm32/src/flash/eeprom.rs new file mode 100644 index 000000000..cc3529eb9 --- /dev/null +++ b/embassy-stm32/src/flash/eeprom.rs @@ -0,0 +1,236 @@ +use embassy_hal_internal::drop::OnDrop; + +use super::{family, Blocking, Error, Flash, EEPROM_BASE, EEPROM_SIZE}; + +#[cfg(eeprom)] +impl<'d> Flash<'d, Blocking> { + // --- Internal helpers --- + + /// Checks if the given offset and size are within the EEPROM bounds. + fn check_eeprom_offset(&self, offset: u32, size: u32) -> Result<(), Error> { + if offset + .checked_add(size) + .filter(|&end| end <= EEPROM_SIZE as u32) + .is_some() + { + Ok(()) + } else { + Err(Error::Size) + } + } + + // --- Unlocked (unsafe, internal) functions --- + + /// Writes a slice of bytes to EEPROM at the given offset without locking. + /// + /// # Safety + /// Caller must ensure EEPROM is unlocked and offset is valid. + unsafe fn eeprom_write_u8_slice_unlocked(&self, offset: u32, data: &[u8]) -> Result<(), Error> { + for (i, &byte) in data.iter().enumerate() { + let addr = EEPROM_BASE as u32 + offset + i as u32; + core::ptr::write_volatile(addr as *mut u8, byte); + family::wait_ready_blocking()?; + family::clear_all_err(); + } + Ok(()) + } + + /// Writes a slice of u16 values to EEPROM at the given offset without locking. + /// + /// # Safety + /// Caller must ensure EEPROM is unlocked and offset is valid and aligned. + unsafe fn eeprom_write_u16_slice_unlocked(&self, offset: u32, data: &[u16]) -> Result<(), Error> { + for (i, &value) in data.iter().enumerate() { + let addr = EEPROM_BASE as u32 + offset + i as u32 * 2; + core::ptr::write_volatile(addr as *mut u16, value); + family::wait_ready_blocking()?; + family::clear_all_err(); + } + Ok(()) + } + + /// Writes a slice of u32 values to EEPROM at the given offset without locking. + /// + /// # Safety + /// Caller must ensure EEPROM is unlocked and offset is valid and aligned. + unsafe fn eeprom_write_u32_slice_unlocked(&self, offset: u32, data: &[u32]) -> Result<(), Error> { + for (i, &value) in data.iter().enumerate() { + let addr = EEPROM_BASE as u32 + offset + i as u32 * 4; + core::ptr::write_volatile(addr as *mut u32, value); + family::wait_ready_blocking()?; + family::clear_all_err(); + } + Ok(()) + } + + // --- Public, safe API --- + + /// Writes a single byte to EEPROM at the given offset. + pub fn eeprom_write_u8(&mut self, offset: u32, value: u8) -> Result<(), Error> { + self.check_eeprom_offset(offset, 1)?; + unsafe { + family::unlock(); + let _on_drop = OnDrop::new(|| family::lock()); + self.eeprom_write_u8_slice_unlocked(offset, core::slice::from_ref(&value))?; + } + Ok(()) + } + + /// Writes a single 16-bit value to EEPROM at the given offset. + /// + /// Returns an error if the offset is not 2-byte aligned. + pub fn eeprom_write_u16(&mut self, offset: u32, value: u16) -> Result<(), Error> { + if offset % 2 != 0 { + return Err(Error::Unaligned); + } + self.check_eeprom_offset(offset, 2)?; + unsafe { + family::unlock(); + let _on_drop = OnDrop::new(|| family::lock()); + self.eeprom_write_u16_slice_unlocked(offset, core::slice::from_ref(&value))?; + } + Ok(()) + } + + /// Writes a single 32-bit value to EEPROM at the given offset. + /// + /// Returns an error if the offset is not 4-byte aligned. + pub fn eeprom_write_u32(&mut self, offset: u32, value: u32) -> Result<(), Error> { + if offset % 4 != 0 { + return Err(Error::Unaligned); + } + self.check_eeprom_offset(offset, 4)?; + unsafe { + family::unlock(); + let _on_drop = OnDrop::new(|| family::lock()); + self.eeprom_write_u32_slice_unlocked(offset, core::slice::from_ref(&value))?; + } + Ok(()) + } + + /// Writes a slice of bytes to EEPROM at the given offset. + pub fn eeprom_write_u8_slice(&mut self, offset: u32, data: &[u8]) -> Result<(), Error> { + self.check_eeprom_offset(offset, data.len() as u32)?; + unsafe { + family::unlock(); + let _on_drop = OnDrop::new(|| family::lock()); + self.eeprom_write_u8_slice_unlocked(offset, data)?; + } + Ok(()) + } + + /// Writes a slice of 16-bit values to EEPROM at the given offset. + /// + /// Returns an error if the offset is not 2-byte aligned. + pub fn eeprom_write_u16_slice(&mut self, offset: u32, data: &[u16]) -> Result<(), Error> { + if offset % 2 != 0 { + return Err(Error::Unaligned); + } + self.check_eeprom_offset(offset, data.len() as u32 * 2)?; + unsafe { + family::unlock(); + let _on_drop = OnDrop::new(|| family::lock()); + self.eeprom_write_u16_slice_unlocked(offset, data)?; + } + Ok(()) + } + + /// Writes a slice of 32-bit values to EEPROM at the given offset. + /// + /// Returns an error if the offset is not 4-byte aligned. + pub fn eeprom_write_u32_slice(&mut self, offset: u32, data: &[u32]) -> Result<(), Error> { + if offset % 4 != 0 { + return Err(Error::Unaligned); + } + self.check_eeprom_offset(offset, data.len() as u32 * 4)?; + unsafe { + family::unlock(); + let _on_drop = OnDrop::new(|| family::lock()); + self.eeprom_write_u32_slice_unlocked(offset, data)?; + } + Ok(()) + } + + /// Writes a byte slice to EEPROM at the given offset, handling alignment. + /// + /// This method will write unaligned prefix and suffix as bytes, and aligned middle as u32. + pub fn eeprom_write_slice(&mut self, offset: u32, data: &[u8]) -> Result<(), Error> { + self.check_eeprom_offset(offset, data.len() as u32)?; + let start = offset; + let misalign = (start % 4) as usize; + let prefix_len = if misalign == 0 { + 0 + } else { + (4 - misalign).min(data.len()) + }; + let (prefix, rest) = data.split_at(prefix_len); + let aligned_len = (rest.len() / 4) * 4; + let (bytes_for_u32_write, suffix) = rest.split_at(aligned_len); + + unsafe { + family::unlock(); + let _on_drop = OnDrop::new(|| family::lock()); + + if !prefix.is_empty() { + self.eeprom_write_u8_slice_unlocked(start, prefix)?; + } + if !bytes_for_u32_write.is_empty() { + let aligned_eeprom_offset = start + prefix_len as u32; + let base_eeprom_addr = EEPROM_BASE as u32 + aligned_eeprom_offset; + for (i, chunk) in bytes_for_u32_write.chunks_exact(4).enumerate() { + // Safely read a u32 from a potentially unaligned pointer into the chunk. + let value = (chunk.as_ptr() as *const u32).read_unaligned(); + let current_eeprom_addr = base_eeprom_addr + (i * 4) as u32; + core::ptr::write_volatile(current_eeprom_addr as *mut u32, value); + family::wait_ready_blocking()?; + family::clear_all_err(); + } + } + if !suffix.is_empty() { + let suffix_offset = start + (prefix_len + aligned_len) as u32; + self.eeprom_write_u8_slice_unlocked(suffix_offset, suffix)?; + } + } + Ok(()) + } + + /// Reads a single byte from EEPROM at the given offset. + pub fn eeprom_read_u8(&self, offset: u32) -> Result { + self.check_eeprom_offset(offset, 1)?; + let addr = EEPROM_BASE as u32 + offset; + Ok(unsafe { core::ptr::read_volatile(addr as *const u8) }) + } + + /// Reads a single 16-bit value from EEPROM at the given offset. + /// + /// Returns an error if the offset is not 2-byte aligned. + pub fn eeprom_read_u16(&self, offset: u32) -> Result { + if offset % 2 != 0 { + return Err(Error::Unaligned); + } + self.check_eeprom_offset(offset, 2)?; + let addr = EEPROM_BASE as u32 + offset; + Ok(unsafe { core::ptr::read_volatile(addr as *const u16) }) + } + + /// Reads a single 32-bit value from EEPROM at the given offset. + /// + /// Returns an error if the offset is not 4-byte aligned. + pub fn eeprom_read_u32(&self, offset: u32) -> Result { + if offset % 4 != 0 { + return Err(Error::Unaligned); + } + self.check_eeprom_offset(offset, 4)?; + let addr = EEPROM_BASE as u32 + offset; + Ok(unsafe { core::ptr::read_volatile(addr as *const u32) }) + } + + /// Reads a slice of bytes from EEPROM at the given offset into the provided buffer. + pub fn eeprom_read_slice(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> { + self.check_eeprom_offset(offset, buf.len() as u32)?; + let addr = EEPROM_BASE as u32 + offset; + let src = unsafe { core::slice::from_raw_parts(addr as *const u8, buf.len()) }; + buf.copy_from_slice(src); + Ok(()) + } +} diff --git a/embassy-stm32/src/flash/f0.rs b/embassy-stm32/src/flash/f0.rs index 402312f68..3f9dbe945 100644 --- a/embassy-stm32/src/flash/f0.rs +++ b/embassy-stm32/src/flash/f0.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, 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.cr().modify(|w| w.set_lock(true)); } diff --git a/embassy-stm32/src/flash/f1f3.rs b/embassy-stm32/src/flash/f1f3.rs index ff7f810ea..bf9ad2893 100644 --- a/embassy-stm32/src/flash/f1f3.rs +++ b/embassy-stm32/src/flash/f1f3.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, 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.cr().modify(|w| w.set_lock(true)); } @@ -64,8 +56,8 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E // BSY bit, because there is a one-cycle delay between // setting the STRT bit and the BSY bit being asserted // by hardware. See STM32F105xx, STM32F107xx device errata, - // section 2.2.8 - #[cfg(stm32f1)] + // section 2.2.8, and also RM0316 Rev 10 section 4.2.3 for + // STM32F3xx series. pac::FLASH.cr().read(); let mut ret: Result<(), Error> = wait_ready_blocking(); diff --git a/embassy-stm32/src/flash/f2.rs b/embassy-stm32/src/flash/f2.rs new file mode 100644 index 000000000..67e380619 --- /dev/null +++ b/embassy-stm32/src/flash/f2.rs @@ -0,0 +1,134 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, AtomicBool, Ordering}; + +use pac::flash::regs::Sr; + +use super::{get_flash_regions, FlashBank, FlashSector, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); + +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); + restore_data_cache_state(); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + blocking_wait_ready() +} + +unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); + + trace!("Blocking erasing sector number {}", sector.snb()); + + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.snb()) + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + clear_all_err(); + restore_data_cache_state(); + 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.sr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + return get_result(sr); + } + } +} + +fn get_result(sr: Sr) -> Result<(), Error> { + if sr.pgserr() { + Err(Error::Seq) + } else if sr.pgperr() { + Err(Error::Parallelism) + } else if sr.pgaerr() { + Err(Error::Unaligned) + } else if sr.wrperr() { + Err(Error::Protected) + } else { + Ok(()) + } +} + +fn save_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Disable data cache during write/erase if there are two banks, see errata 2.2.12 + let dcen = pac::FLASH.acr().read().dcen(); + DATA_CACHE_WAS_ENABLED.store(dcen, Ordering::Relaxed); + if dcen { + pac::FLASH.acr().modify(|w| w.set_dcen(false)); + } + } +} + +fn restore_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Restore data cache if it was enabled + let dcen = DATA_CACHE_WAS_ENABLED.load(Ordering::Relaxed); + if dcen { + // Reset data cache before we enable it again + pac::FLASH.acr().modify(|w| w.set_dcrst(true)); + pac::FLASH.acr().modify(|w| w.set_dcrst(false)); + pac::FLASH.acr().modify(|w| w.set_dcen(true)) + } + } +} diff --git a/embassy-stm32/src/flash/f4.rs b/embassy-stm32/src/flash/f4.rs index d0bb957ee..62e0492b5 100644 --- a/embassy-stm32/src/flash/f4.rs +++ b/embassy-stm32/src/flash/f4.rs @@ -3,176 +3,11 @@ use core::sync::atomic::{fence, AtomicBool, Ordering}; use embassy_sync::waitqueue::AtomicWaker; use pac::flash::regs::Sr; -use pac::FLASH_SIZE; -use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{get_flash_regions, FlashBank, FlashSector, WRITE_SIZE}; +use crate::_generated::FLASH_SIZE; use crate::flash::Error; use crate::pac; -#[allow(missing_docs)] // TODO -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -mod alt_regions { - use core::marker::PhantomData; - - use embassy_hal_internal::PeripheralRef; - use stm32_metapac::FLASH_SIZE; - - use crate::_generated::flash_regions::{BANK1_REGION1, BANK1_REGION2, BANK1_REGION3}; - use crate::flash::{asynch, Async, Bank1Region1, Bank1Region2, Blocking, Error, Flash, FlashBank, FlashRegion}; - use crate::peripherals::FLASH; - - pub const ALT_BANK1_REGION3: FlashRegion = FlashRegion { - size: 3 * BANK1_REGION3.erase_size, - ..BANK1_REGION3 - }; - pub const ALT_BANK2_REGION1: FlashRegion = FlashRegion { - bank: FlashBank::Bank2, - base: BANK1_REGION1.base + FLASH_SIZE as u32 / 2, - ..BANK1_REGION1 - }; - pub const ALT_BANK2_REGION2: FlashRegion = FlashRegion { - bank: FlashBank::Bank2, - base: BANK1_REGION2.base + FLASH_SIZE as u32 / 2, - ..BANK1_REGION2 - }; - pub const ALT_BANK2_REGION3: FlashRegion = FlashRegion { - bank: FlashBank::Bank2, - base: BANK1_REGION3.base + FLASH_SIZE as u32 / 2, - size: 3 * BANK1_REGION3.erase_size, - ..BANK1_REGION3 - }; - - pub const ALT_FLASH_REGIONS: [&FlashRegion; 6] = [ - &BANK1_REGION1, - &BANK1_REGION2, - &ALT_BANK1_REGION3, - &ALT_BANK2_REGION1, - &ALT_BANK2_REGION2, - &ALT_BANK2_REGION3, - ]; - - pub struct AltBank1Region3<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); - pub struct AltBank2Region1<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); - pub struct AltBank2Region2<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); - pub struct AltBank2Region3<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); - - pub struct AltFlashLayout<'d, MODE = Async> { - pub bank1_region1: Bank1Region1<'d, MODE>, - pub bank1_region2: Bank1Region2<'d, MODE>, - pub bank1_region3: AltBank1Region3<'d, MODE>, - pub bank2_region1: AltBank2Region1<'d, MODE>, - pub bank2_region2: AltBank2Region2<'d, MODE>, - pub bank2_region3: AltBank2Region3<'d, MODE>, - } - - impl<'d> Flash<'d> { - pub fn into_alt_regions(self) -> AltFlashLayout<'d, Async> { - assert!(!super::is_default_layout()); - - // SAFETY: We never expose the cloned peripheral references, and their instance is not public. - // Also, all async flash region operations are protected with a mutex. - let p = self.inner; - AltFlashLayout { - bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - } - } - - pub fn into_alt_blocking_regions(self) -> AltFlashLayout<'d, Blocking> { - assert!(!super::is_default_layout()); - - // SAFETY: We never expose the cloned peripheral references, and their instance is not public. - // Also, all blocking flash region operations are protected with a cs. - let p = self.inner; - AltFlashLayout { - bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - } - } - } - - macro_rules! foreach_altflash_region { - ($type_name:ident, $region:ident) => { - impl $type_name<'_, MODE> { - pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { - crate::flash::common::blocking_read(self.0.base, self.0.size, offset, bytes) - } - } - - impl $type_name<'_, Async> { - pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { - self.blocking_read(offset, bytes) - } - - pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { - let _guard = asynch::REGION_ACCESS.lock().await; - unsafe { asynch::write_chunked(self.0.base, self.0.size, offset, bytes).await } - } - - pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { - let _guard = asynch::REGION_ACCESS.lock().await; - unsafe { asynch::erase_sectored(self.0.base, from, to).await } - } - } - - impl embedded_storage::nor_flash::ErrorType for $type_name<'_, MODE> { - type Error = Error; - } - - impl embedded_storage::nor_flash::ReadNorFlash for $type_name<'_, MODE> { - const READ_SIZE: usize = crate::flash::READ_SIZE; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(offset, bytes) - } - - fn capacity(&self) -> usize { - self.0.size as usize - } - } - - impl embedded_storage_async::nor_flash::ReadNorFlash for $type_name<'_, Async> { - const READ_SIZE: usize = crate::flash::READ_SIZE; - - async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.read(offset, bytes).await - } - - fn capacity(&self) -> usize { - self.0.size as usize - } - } - - impl embedded_storage_async::nor_flash::NorFlash for $type_name<'_, Async> { - const WRITE_SIZE: usize = $region.write_size as usize; - const ERASE_SIZE: usize = $region.erase_size as usize; - - async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes).await - } - - async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to).await - } - } - }; - } - - foreach_altflash_region!(AltBank1Region3, ALT_BANK1_REGION3); - foreach_altflash_region!(AltBank2Region1, ALT_BANK2_REGION1); - foreach_altflash_region!(AltBank2Region2, ALT_BANK2_REGION2); - foreach_altflash_region!(AltBank2Region3, ALT_BANK2_REGION3); -} - -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -pub use alt_regions::*; static WAKER: AtomicWaker = AtomicWaker::new(); static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); @@ -183,30 +18,6 @@ impl FlashSector { } } -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -pub(crate) fn is_default_layout() -> bool { - !pac::FLASH.optcr().read().db1m() -} - -#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] -pub(crate) const fn is_default_layout() -> bool { - true -} - -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -pub fn get_flash_regions() -> &'static [&'static FlashRegion] { - if is_default_layout() { - &FLASH_REGIONS - } else { - &ALT_FLASH_REGIONS - } -} - -#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] -pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn on_interrupt() { // Clear IRQ flags pac::FLASH.sr().write(|w| { @@ -469,7 +280,7 @@ fn pa12_is_output_pull_low() -> bool { use pac::GPIOA; const PIN: usize = 12; GPIOA.moder().read().moder(PIN) == vals::Moder::OUTPUT - && GPIOA.pupdr().read().pupdr(PIN) == vals::Pupdr::PULLDOWN + && GPIOA.pupdr().read().pupdr(PIN) == vals::Pupdr::PULL_DOWN && GPIOA.odr().read().odr(PIN) == vals::Odr::LOW } @@ -485,71 +296,83 @@ mod tests { const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; const LARGE_SECTOR_SIZE: u32 = 128 * 1024; - let assert_sector = |snb: u8, index_in_bank: u8, start: u32, size: u32, address: u32| { - let sector = get_sector(address, &FLASH_REGIONS); - assert_eq!(snb, sector.snb()); - assert_eq!( - FlashSector { - bank: FlashBank::Bank1, - index_in_bank, - start, - size - }, - sector - ); - }; + if !cfg!(feature = "dual-bank") { + let assert_sector = |snb: u8, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, crate::flash::get_flash_regions()); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank: sector.bank, + index_in_bank, + start, + size + }, + sector + ); + }; - assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); - assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); - assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); - assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); - assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); - assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); - assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); - assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); - assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); - assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } else { + let assert_sector = |snb: u8, bank: FlashBank, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, crate::flash::get_flash_regions()); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank, + index_in_bank, + start, + size + }, + sector + ) + }; - let assert_sector = |snb: u8, bank: FlashBank, index_in_bank: u8, start: u32, size: u32, address: u32| { - let sector = get_sector(address, &ALT_FLASH_REGIONS); - assert_eq!(snb, sector.snb()); - assert_eq!( - FlashSector { - bank, - index_in_bank, - start, - size - }, - sector - ) - }; + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); - assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); - assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); - assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); - assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); - assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); - assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); - assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); - assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); - assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); - assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_0000); + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_3FFF); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_C000); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_FFFF); - assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_0000); - assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_3FFF); - assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_C000); - assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_FFFF); + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_0000); + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_FFFF); - assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_0000); - assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_FFFF); - - assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080A_0000); - assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080B_FFFF); - assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); - assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080A_0000); + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080B_FFFF); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } + } +} + +#[cfg(all(bank_setup_configurable))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optcr().read().db1m() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the db1m value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optcr().read().db1m() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the db1m value in the user option bytes or configure embassy to use single-bank config"); } } diff --git a/embassy-stm32/src/flash/f7.rs b/embassy-stm32/src/flash/f7.rs index 09ebe9db9..0547c747a 100644 --- a/embassy-stm32/src/flash/f7.rs +++ b/embassy-stm32/src/flash/f7.rs @@ -1,16 +1,14 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, 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 +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } } pub(crate) unsafe fn lock() { @@ -53,7 +51,7 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { pac::FLASH.cr().modify(|w| { w.set_ser(true); - w.set_snb(sector.index_in_bank) + w.set_snb(sector.snb()) }); pac::FLASH.cr().modify(|w| { @@ -118,7 +116,7 @@ mod tests { start, size }, - get_sector(address, &FLASH_REGIONS) + get_sector(address, crate::flash::get_flash_regions()) ) }; @@ -137,7 +135,7 @@ mod tests { } #[test] - #[cfg(stm32f769)] + #[cfg(all(stm32f769, feature = "single-bank"))] fn can_get_sector() { const SMALL_SECTOR_SIZE: u32 = 32 * 1024; const MEDIUM_SECTOR_SIZE: u32 = 128 * 1024; @@ -151,7 +149,7 @@ mod tests { start, size }, - get_sector(address, &FLASH_REGIONS) + get_sector(address, crate::flash::get_flash_regions()) ) }; @@ -168,4 +166,61 @@ mod tests { assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080C_0000); assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); } + + #[test] + #[cfg(all(stm32f769, feature = "dual-bank"))] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 16 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; + const LARGE_SECTOR_SIZE: u32 = 128 * 1024; + + let assert_sector = |index_in_bank: u8, start: u32, size: u32, address: u32, snb: u8, bank: FlashBank| { + assert_eq!( + FlashSector { + bank: bank, + index_in_bank, + start, + size + }, + get_sector(address, crate::flash::get_flash_regions()) + ); + assert_eq!(get_sector(address, crate::flash::get_flash_regions()).snb(), snb); + }; + + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000, 0x00, FlashBank::Bank1); + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF, 0x00, FlashBank::Bank1); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000, 0x03, FlashBank::Bank1); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF, 0x03, FlashBank::Bank1); + + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000, 0x04, FlashBank::Bank1); + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF, 0x04, FlashBank::Bank1); + + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000, 0x05, FlashBank::Bank1); + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF, 0x05, FlashBank::Bank1); + assert_sector(10, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080C_0000, 0x0A, FlashBank::Bank1); + assert_sector(10, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080D_FFFF, 0x0A, FlashBank::Bank1); + + assert_sector(0, 0x0810_0000, SMALL_SECTOR_SIZE, 0x0810_0000, 0x10, FlashBank::Bank2); + assert_sector(0, 0x0810_0000, SMALL_SECTOR_SIZE, 0x0810_3FFF, 0x10, FlashBank::Bank2); + assert_sector(3, 0x0810_C000, SMALL_SECTOR_SIZE, 0x0810_C000, 0x13, FlashBank::Bank2); + assert_sector(3, 0x0810_C000, SMALL_SECTOR_SIZE, 0x0810_FFFF, 0x13, FlashBank::Bank2); + + assert_sector(4, 0x0811_0000, MEDIUM_SECTOR_SIZE, 0x0811_0000, 0x14, FlashBank::Bank2); + assert_sector(4, 0x0811_0000, MEDIUM_SECTOR_SIZE, 0x0811_FFFF, 0x14, FlashBank::Bank2); + + assert_sector(5, 0x0812_0000, LARGE_SECTOR_SIZE, 0x0812_0000, 0x15, FlashBank::Bank2); + assert_sector(5, 0x0812_0000, LARGE_SECTOR_SIZE, 0x0813_FFFF, 0x15, FlashBank::Bank2); + assert_sector(10, 0x081C_0000, LARGE_SECTOR_SIZE, 0x081C_0000, 0x1A, FlashBank::Bank2); + assert_sector(10, 0x081C_0000, LARGE_SECTOR_SIZE, 0x081D_FFFF, 0x1A, FlashBank::Bank2); + } +} + +#[cfg(all(bank_setup_configurable))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && !pac::FLASH.optcr().read().n_dbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the ndbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && pac::FLASH.optcr().read().n_dbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the ndbank value in the user option bytes or configure embassy to use single-bank config"); + } } diff --git a/embassy-stm32/src/flash/g.rs b/embassy-stm32/src/flash/g.rs index 01a0c603f..bc1fd360c 100644 --- a/embassy-stm32/src/flash/g.rs +++ b/embassy-stm32/src/flash/g.rs @@ -3,24 +3,16 @@ use core::sync::atomic::{fence, Ordering}; use cortex_m::interrupt; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, 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.cr().modify(|w| w.set_lock(true)); } pub(crate) unsafe fn unlock() { // Wait, while the memory interface is busy. - while pac::FLASH.sr().read().bsy() {} + wait_busy(); // Unlock flash if pac::FLASH.cr().read().lock() { @@ -53,12 +45,17 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; - while pac::FLASH.sr().read().bsy() {} + wait_busy(); clear_all_err(); interrupt::free(|_| { pac::FLASH.cr().modify(|w| { w.set_per(true); + #[cfg(any(flash_g0x0, flash_g0x1, flash_g4c3))] + w.set_bker(sector.bank == crate::flash::FlashBank::Bank2); + #[cfg(flash_g0x0)] + w.set_pnb(idx as u16); + #[cfg(not(flash_g0x0))] w.set_pnb(idx as u8); w.set_strt(true); }); @@ -94,3 +91,33 @@ pub(crate) unsafe fn clear_all_err() { // This clears all "write 1 to clear" bits. pac::FLASH.sr().modify(|_| {}); } + +#[cfg(any(flash_g0x0, flash_g0x1))] +fn wait_busy() { + while pac::FLASH.sr().read().bsy() | pac::FLASH.sr().read().bsy2() {} +} + +#[cfg(not(any(flash_g0x0, flash_g0x1)))] +fn wait_busy() { + while pac::FLASH.sr().read().bsy() {} +} + +#[cfg(all(bank_setup_configurable, any(flash_g4c2, flash_g4c3, flash_g4c4)))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use single-bank config"); + } +} + +#[cfg(all(bank_setup_configurable, flash_g0x1))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dual_bank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dual_bank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dual_bank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dual_bank value in the user option bytes or configure embassy to use single-bank config"); + } +} diff --git a/embassy-stm32/src/flash/h5.rs b/embassy-stm32/src/flash/h5.rs new file mode 100644 index 000000000..fd9bfcc75 --- /dev/null +++ b/embassy-stm32/src/flash/h5.rs @@ -0,0 +1,166 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashSector, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub(crate) unsafe fn lock() { + if !pac::FLASH.nscr().read().lock() { + pac::FLASH.nscr().modify(|r| { + r.set_lock(true); + }); + } +} + +pub(crate) unsafe fn unlock() { + // TODO: check locked first + while pac::FLASH.nssr().read().bsy() { + #[cfg(feature = "defmt")] + defmt::trace!("busy") + } + + // only unlock if locked to begin with + 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); +} + +pub(crate) unsafe fn disable_blocking_write() {} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + // // We cannot have the write setup sequence in begin_write as it depends on the address + // let bank = if start_address < BANK1_REGION.end() { + // pac::FLASH.bank(0) + // } else { + // pac::FLASH.bank(1) + // }; + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + clear_all_err(); + + pac::FLASH.nscr().write(|w| { + w.set_pg(true); + // w.set_psize(2); // 32 bits at once + }); + + let mut res = None; + let mut address = start_address; + // TODO: see write size + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + res = Some(blocking_wait_ready().map_err(|e| { + error!("write err"); + e + })); + pac::FLASH.nssr().modify(|w| { + if w.eop() { + w.set_eop(true); + } + }); + if unwrap!(res).is_err() { + break; + } + } + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + pac::FLASH.nscr().write(|w| w.set_pg(false)); + + unwrap!(res) +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + // pac::FLASH.wrp2r_cur().read().wrpsg() + // TODO: write protection check + if pac::FLASH.nscr().read().lock() == true { + error!("flash locked"); + } + + loop { + let sr = pac::FLASH.nssr().read(); + if !sr.bsy() && !sr.dbne() { + break; + } + } + clear_all_err(); + + pac::FLASH.nscr().modify(|r| { + // TODO: later check bank swap + r.set_bksel(match sector.bank { + crate::flash::FlashBank::Bank1 => stm32_metapac::flash::vals::NscrBksel::B_0X0, + crate::flash::FlashBank::Bank2 => stm32_metapac::flash::vals::NscrBksel::B_0X1, + _ => unreachable!(), + }); + r.set_snb(sector.index_in_bank); + r.set_ser(true); + }); + + pac::FLASH.nscr().modify(|r| { + r.set_strt(true); + }); + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + let ret: Result<(), Error> = blocking_wait_ready().map_err(|e| { + error!("erase err"); + e + }); + + pac::FLASH.nscr().modify(|w| w.set_ser(false)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.nssr().modify(|_| {}) +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.nssr().read(); + + if !sr.bsy() { + if sr.optchangeerr() { + error!("optchangeerr"); + return Err(Error::Prog); + } + if sr.obkwerr() { + error!("obkwerr"); + return Err(Error::Seq); + } + if sr.obkerr() { + error!("obkerr"); + return Err(Error::Seq); + } + if sr.incerr() { + error!("incerr"); + return Err(Error::Unaligned); + } + if sr.strberr() { + error!("strberr"); + return Err(Error::Parallelism); + } + if sr.wrperr() { + error!("protected"); + return Err(Error::Protected); + } + + return Ok(()); + } + } +} diff --git a/embassy-stm32/src/flash/h50.rs b/embassy-stm32/src/flash/h50.rs index 82e77d130..f8e210556 100644 --- a/embassy-stm32/src/flash/h50.rs +++ b/embassy-stm32/src/flash/h50.rs @@ -8,17 +8,9 @@ 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 super::{Error, FlashBank, FlashSector, 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)); } @@ -55,6 +47,7 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) } pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + assert!(sector.bank != FlashBank::Otp); assert!(sector.index_in_bank < 8); while busy() {} @@ -67,6 +60,7 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E (FlashBank::Bank2, true) => Bksel::BANK1, (FlashBank::Bank2, false) => Bksel::BANK2, (FlashBank::Bank1, true) => Bksel::BANK2, + _ => unreachable!(), }); w.set_snb(sector.index_in_bank); w.set_ser(true); diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs index 254915381..f1d84101c 100644 --- a/embassy-stm32/src/flash/h7.rs +++ b/embassy-stm32/src/flash/h7.rs @@ -1,22 +1,14 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, BANK1_REGION, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, BANK1_REGION, FLASH_REGIONS, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - const fn is_dual_bank() -> bool { FLASH_REGIONS.len() >= 2 } -pub(crate) fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.bank(0).cr().modify(|w| w.set_lock(true)); if is_dual_bank() { diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs index a0bfeb395..1b82704ec 100644 --- a/embassy-stm32/src/flash/l.rs +++ b/embassy-stm32/src/flash/l.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, 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() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().modify(|w| w.set_lock(true)); @@ -23,6 +15,9 @@ pub(crate) unsafe fn lock() { w.set_prglock(true); w.set_pelock(true); }); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nslock(true)); } pub(crate) unsafe fn unlock() { @@ -46,6 +41,14 @@ pub(crate) unsafe fn unlock() { pac::FLASH.prgkeyr().write_value(0x1314_1516); } } + + #[cfg(any(flash_l5))] + { + if pac::FLASH.nscr().read().nslock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } + } } pub(crate) unsafe fn enable_blocking_write() { @@ -53,11 +56,17 @@ pub(crate) unsafe fn enable_blocking_write() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().write(|w| w.set_pg(true)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(true)); } pub(crate) unsafe fn disable_blocking_write() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().write(|w| w.set_pg(false)); + + #[cfg(any(flash_l5))] + pac::FLASH.nscr().write(|w| w.set_nspg(false)); } pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { @@ -84,13 +93,25 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E write_volatile(sector.start as *mut u32, 0xFFFFFFFF); } - #[cfg(any(flash_wl, flash_wb, flash_l4))] + #[cfg(any(flash_wl, flash_wb, flash_l4, flash_l5))] { let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; #[cfg(flash_l4)] let (idx, bank) = if idx > 255 { (idx - 256, true) } else { (idx, false) }; + #[cfg(flash_l5)] + let (idx, bank) = if pac::FLASH.optr().read().dbank() { + if idx > 255 { + (idx - 256, Some(true)) + } else { + (idx, Some(false)) + } + } else { + (idx, None) + }; + + #[cfg(not(flash_l5))] pac::FLASH.cr().modify(|w| { w.set_per(true); w.set_pnb(idx as u8); @@ -101,6 +122,16 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_l4))] w.set_bker(bank); }); + + #[cfg(flash_l5)] + pac::FLASH.nscr().modify(|w| { + w.set_nsper(true); + w.set_nspnb(idx as u8); + if let Some(bank) = bank { + w.set_nsbker(bank); + } + w.set_nsstrt(true); + }); } let ret: Result<(), Error> = wait_ready_blocking(); @@ -108,6 +139,9 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().modify(|w| w.set_per(false)); + #[cfg(any(flash_l5))] + pac::FLASH.nscr().modify(|w| w.set_nsper(false)); + #[cfg(any(flash_l0, flash_l1))] pac::FLASH.pecr().modify(|w| { w.set_erase(false); @@ -121,42 +155,98 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E pub(crate) unsafe fn clear_all_err() { // read and write back the same value. // This clears all "write 1 to clear" bits. + #[cfg(not(flash_l5))] pac::FLASH.sr().modify(|_| {}); + + #[cfg(flash_l5)] + pac::FLASH.nssr().modify(|_| {}); } -unsafe fn wait_ready_blocking() -> Result<(), Error> { +pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> { loop { - let sr = pac::FLASH.sr().read(); + #[cfg(not(flash_l5))] + { + let sr = pac::FLASH.sr().read(); - if !sr.bsy() { - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.progerr() { - return Err(Error::Prog); + if !sr.bsy() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.progerr() { + return Err(Error::Prog); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + if sr.sizerr() { + return Err(Error::Size); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.miserr() { + return Err(Error::Miss); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + if sr.pgserr() { + return Err(Error::Seq); + } + + return Ok(()); } + } - if sr.wrperr() { - return Err(Error::Protected); + #[cfg(flash_l5)] + { + let nssr = pac::FLASH.nssr().read(); + + if !nssr.nsbsy() { + if nssr.nsprogerr() { + return Err(Error::Prog); + } + + if nssr.nswrperr() { + return Err(Error::Protected); + } + + if nssr.nspgaerr() { + return Err(Error::Unaligned); + } + + if nssr.nssizerr() { + return Err(Error::Size); + } + + if nssr.nspgserr() { + return Err(Error::Seq); + } + + return Ok(()); } - - if sr.pgaerr() { - return Err(Error::Unaligned); - } - - if sr.sizerr() { - return Err(Error::Size); - } - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.miserr() { - return Err(Error::Miss); - } - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - if sr.pgserr() { - return Err(Error::Seq); - } - - return Ok(()); } } } + +#[cfg(all(bank_setup_configurable, flash_l5))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use single-bank config"); + } +} + +#[cfg(all(bank_setup_configurable, flash_l4))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dualbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dualbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dualbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dualbank value in the user option bytes or configure embassy to use single-bank config"); + } +} diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index ce2d1a04c..a3f9e00f7 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -5,27 +5,25 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; mod asynch; #[cfg(flash)] mod common; +#[cfg(eeprom)] +mod eeprom; #[cfg(flash_f4)] pub use asynch::InterruptHandler; #[cfg(flash)] pub use common::*; +#[cfg(eeprom)] +#[allow(unused_imports)] +pub use eeprom::*; pub use crate::_generated::flash_regions::*; -pub use crate::_generated::MAX_ERASE_SIZE; -pub use crate::pac::{FLASH_BASE, FLASH_SIZE, WRITE_SIZE}; - -/// Get whether the default flash layout is being used. -/// -/// In some chips, dual-bank is not default. This will then return `false` -/// when dual-bank is enabled. -pub fn is_default_layout() -> bool { - family::is_default_layout() -} +#[cfg(eeprom)] +pub use crate::_generated::{EEPROM_BASE, EEPROM_SIZE}; +pub use crate::_generated::{FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE}; /// Get all flash regions. pub fn get_flash_regions() -> &'static [&'static FlashRegion] { - family::get_flash_regions() + &FLASH_REGIONS } /// Read size (always 1) @@ -89,23 +87,29 @@ pub enum FlashBank { Bank1 = 0, /// Bank 2 Bank2 = 1, + /// OTP region, + Otp, } - -#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_wl, flash_wb), path = "l.rs")] +#[cfg(all(eeprom, not(any(flash_l0, flash_l1))))] +compile_error!("The 'eeprom' cfg is enabled for a non-L0/L1 chip family. This is an unsupported configuration."); +#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb), path = "l.rs")] #[cfg_attr(flash_f0, path = "f0.rs")] #[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] +#[cfg_attr(flash_f2, path = "f2.rs")] #[cfg_attr(flash_f4, path = "f4.rs")] #[cfg_attr(flash_f7, path = "f7.rs")] -#[cfg_attr(any(flash_g0, flash_g4c2, flash_g4c3, flash_g4c4), path = "g.rs")] +#[cfg_attr(any(flash_g0x0, flash_g0x1, flash_g4c2, flash_g4c3, flash_g4c4), 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_h5, path = "h5.rs")] #[cfg_attr(flash_h50, path = "h50.rs")] #[cfg_attr(flash_u0, path = "u0.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_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0 + flash_l0, flash_l1, flash_l4, flash_l5, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, + flash_f7, flash_g0x0, flash_g0x1, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, + flash_h50, flash_u0, flash_h5, )), path = "other.rs" )] diff --git a/embassy-stm32/src/flash/other.rs b/embassy-stm32/src/flash/other.rs index 20f84a72f..293a79be3 100644 --- a/embassy-stm32/src/flash/other.rs +++ b/embassy-stm32/src/flash/other.rs @@ -1,14 +1,6 @@ #![allow(unused)] -use super::{Error, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; - -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} +use super::{Error, FlashSector, WRITE_SIZE}; pub(crate) unsafe fn lock() { unimplemented!(); diff --git a/embassy-stm32/src/flash/u0.rs b/embassy-stm32/src/flash/u0.rs index bfdbd15a5..68d847eca 100644 --- a/embassy-stm32/src/flash/u0.rs +++ b/embassy-stm32/src/flash/u0.rs @@ -3,18 +3,10 @@ use core::sync::atomic::{fence, Ordering}; use cortex_m::interrupt; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, 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.cr().modify(|w| w.set_lock(true)); } diff --git a/embassy-stm32/src/flash/u5.rs b/embassy-stm32/src/flash/u5.rs index 0601017ce..131caa195 100644 --- a/embassy-stm32/src/flash/u5.rs +++ b/embassy-stm32/src/flash/u5.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashBank, FlashSector, 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() { #[cfg(feature = "trustzone-secure")] pac::FLASH.seccr().modify(|w| w.set_lock(true)); @@ -70,12 +62,24 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E #[cfg(feature = "trustzone-secure")] pac::FLASH.seccr().modify(|w| { w.set_per(pac::flash::vals::SeccrPer::B_0X1); - w.set_pnb(sector.index_in_bank) + w.set_pnb(sector.index_in_bank); + // TODO: add check for bank swap + w.set_bker(match sector.bank { + FlashBank::Bank1 => pac::flash::vals::SeccrBker::B_0X0, + FlashBank::Bank2 => pac::flash::vals::SeccrBker::B_0X1, + _ => unreachable!(), + }); }); #[cfg(not(feature = "trustzone-secure"))] pac::FLASH.nscr().modify(|w| { w.set_per(pac::flash::vals::NscrPer::B_0X1); - w.set_pnb(sector.index_in_bank) + w.set_pnb(sector.index_in_bank); + // TODO: add check for bank swap + w.set_bker(match sector.bank { + FlashBank::Bank1 => pac::flash::vals::NscrBker::B_0X0, + FlashBank::Bank2 => pac::flash::vals::NscrBker::B_0X1, + _ => unreachable!(), + }); }); #[cfg(feature = "trustzone-secure")] diff --git a/embassy-stm32/src/fmc.rs b/embassy-stm32/src/fmc.rs index 83b49a3dd..71ca775cb 100644 --- a/embassy-stm32/src/fmc.rs +++ b/embassy-stm32/src/fmc.rs @@ -1,10 +1,10 @@ //! Flexible Memory Controller (FMC) / Flexible Static Memory Controller (FSMC) use core::marker::PhantomData; -use embassy_hal_internal::into_ref; +use embassy_hal_internal::PeripheralType; use crate::gpio::{AfType, OutputType, Pull, Speed}; -use crate::{rcc, Peripheral}; +use crate::{rcc, Peri}; /// FMC driver pub struct Fmc<'d, T: Instance> { @@ -21,7 +21,7 @@ where /// /// **Note:** This is currently used to provide access to some basic FMC functions /// for manual configuration for memory types that stm32-fmc does not support. - pub fn new_raw(_instance: impl Peripheral

+ 'd) -> Self { + pub fn new_raw(_instance: Peri<'d, T>) -> Self { Self { peri: PhantomData } } @@ -74,8 +74,7 @@ where macro_rules! config_pins { ($($pin:ident),*) => { - into_ref!($($pin),*); - $( + $( $pin.set_as_af($pin.af_num(), AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up)); )* }; @@ -92,12 +91,12 @@ macro_rules! fmc_sdram_constructor { )) => { /// Create a new FMC instance. pub fn $name( - _instance: impl Peripheral

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

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

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

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

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

> + 'd),*, + _instance: Peri<'d, T>, + $($addr_pin_name: Peri<'d, impl $addr_signal>),*, + $($ba_pin_name: Peri<'d, impl $ba_signal>),*, + $($d_pin_name: Peri<'d, impl $d_signal>),*, + $($nbl_pin_name: Peri<'d, impl $nbl_signal>),*, + $($ctrl_pin_name: Peri<'d, impl $ctrl_signal>),*, chip: CHIP ) -> stm32_fmc::Sdram, CHIP> { @@ -245,7 +244,7 @@ trait SealedInstance: crate::rcc::RccPeripheral { /// FMC instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static {} +pub trait Instance: SealedInstance + PeripheralType + 'static {} foreach_peripheral!( (fmc, $inst:ident) => { diff --git a/embassy-stm32/src/fmt.rs b/embassy-stm32/src/fmt.rs index 8ca61bc39..b6ae24ee8 100644 --- a/embassy-stm32/src/fmt.rs +++ b/embassy-stm32/src/fmt.rs @@ -6,6 +6,28 @@ use core::fmt::{Debug, Display, LowerHex}; #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); +#[collapse_debuginfo(yes)] +macro_rules! rcc_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "unchecked-overclocking"))] + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + #[cfg(feature = "unchecked-overclocking")] + { + #[cfg(feature = "log")] + ::log::warn!("`rcc_assert!` skipped: `unchecked-overclocking` feature is enabled."); + #[cfg(feature = "defmt")] + ::defmt::warn!("`rcc_assert!` skipped: `unchecked-overclocking` feature is enabled."); + } + } + }; +} + #[collapse_debuginfo(yes)] macro_rules! assert { ($($x:tt)*) => { diff --git a/embassy-stm32/src/gpio.rs b/embassy-stm32/src/gpio.rs index 87519f51e..bb37c4194 100644 --- a/embassy-stm32/src/gpio.rs +++ b/embassy-stm32/src/gpio.rs @@ -4,10 +4,10 @@ use core::convert::Infallible; use critical_section::CriticalSection; -use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_hal_internal::{impl_peripheral, Peri, PeripheralType}; use crate::pac::gpio::{self, vals}; -use crate::{pac, peripherals, Peripheral}; +use crate::peripherals; /// GPIO flexible pin. /// @@ -15,7 +15,7 @@ use crate::{pac, peripherals, Peripheral}; /// 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> { - pub(crate) pin: PeripheralRef<'d, AnyPin>, + pub(crate) pin: Peri<'d, AnyPin>, } impl<'d> Flex<'d> { @@ -25,10 +25,9 @@ impl<'d> Flex<'d> { /// before the pin is put into output mode. /// #[inline] - pub fn new(pin: impl Peripheral

+ 'd) -> Self { - into_ref!(pin); + pub fn new(pin: Peri<'d, impl Pin>) -> Self { // Pin will be in disconnected state. - Self { pin: pin.map_into() } + Self { pin: pin.into() } } /// Put the pin into input mode. @@ -61,7 +60,7 @@ impl<'d> Flex<'d> { #[cfg(gpio_v2)] { r.pupdr().modify(|w| w.set_pupdr(n, pull.to_pupdr())); - r.otyper().modify(|w| w.set_ot(n, vals::Ot::PUSHPULL)); + r.otyper().modify(|w| w.set_ot(n, vals::Ot::PUSH_PULL)); r.moder().modify(|w| w.set_moder(n, vals::Moder::INPUT)); } }); @@ -82,13 +81,13 @@ impl<'d> Flex<'d> { { r.cr(n / 8).modify(|w| { w.set_mode(n % 8, speed.to_mode()); - w.set_cnf_out(n % 8, vals::CnfOut::PUSHPULL); + w.set_cnf_out(n % 8, vals::CnfOut::PUSH_PULL); }); } #[cfg(gpio_v2)] { r.pupdr().modify(|w| w.set_pupdr(n, vals::Pupdr::FLOATING)); - r.otyper().modify(|w| w.set_ot(n, vals::Ot::PUSHPULL)); + r.otyper().modify(|w| w.set_ot(n, vals::Ot::PUSH_PULL)); r.ospeedr().modify(|w| w.set_ospeedr(n, speed.to_ospeedr())); r.moder().modify(|w| w.set_moder(n, vals::Moder::OUTPUT)); } @@ -112,7 +111,7 @@ impl<'d> Flex<'d> { let r = self.pin.block(); let n = self.pin.pin() as usize; r.cr(n / 8).modify(|w| w.set_mode(n % 8, speed.to_mode())); - r.cr(n / 8).modify(|w| w.set_cnf_out(n % 8, vals::CnfOut::OPENDRAIN)); + r.cr(n / 8).modify(|w| w.set_cnf_out(n % 8, vals::CnfOut::OPEN_DRAIN)); }); #[cfg(gpio_v2)] @@ -130,7 +129,7 @@ impl<'d> Flex<'d> { let r = self.pin.block(); let n = self.pin.pin() as usize; r.pupdr().modify(|w| w.set_pupdr(n, pull.to_pupdr())); - r.otyper().modify(|w| w.set_ot(n, vals::Ot::OPENDRAIN)); + r.otyper().modify(|w| w.set_ot(n, vals::Ot::OPEN_DRAIN)); r.ospeedr().modify(|w| w.set_ospeedr(n, speed.to_ospeedr())); r.moder().modify(|w| w.set_moder(n, vals::Moder::OUTPUT)); }); @@ -253,8 +252,8 @@ impl Pull { const fn to_pupdr(self) -> vals::Pupdr { match self { Pull::None => vals::Pupdr::FLOATING, - Pull::Up => vals::Pupdr::PULLUP, - Pull::Down => vals::Pupdr::PULLDOWN, + Pull::Up => vals::Pupdr::PULL_UP, + Pull::Down => vals::Pupdr::PULL_DOWN, } } } @@ -293,11 +292,11 @@ impl Speed { #[cfg(gpio_v2)] const fn to_ospeedr(self: Speed) -> vals::Ospeedr { match self { - Speed::Low => vals::Ospeedr::LOWSPEED, - Speed::Medium => vals::Ospeedr::MEDIUMSPEED, + Speed::Low => vals::Ospeedr::LOW_SPEED, + Speed::Medium => vals::Ospeedr::MEDIUM_SPEED, #[cfg(not(syscfg_f0))] - Speed::High => vals::Ospeedr::HIGHSPEED, - Speed::VeryHigh => vals::Ospeedr::VERYHIGHSPEED, + Speed::High => vals::Ospeedr::HIGH_SPEED, + Speed::VeryHigh => vals::Ospeedr::VERY_HIGH_SPEED, } } } @@ -310,7 +309,7 @@ pub struct Input<'d> { impl<'d> Input<'d> { /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. #[inline] - pub fn new(pin: impl Peripheral

+ 'd, pull: Pull) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(pull); Self { pin } @@ -375,7 +374,7 @@ pub struct Output<'d> { 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

+ 'd, initial_output: Level, speed: Speed) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level, speed: Speed) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -440,7 +439,7 @@ pub struct OutputOpenDrain<'d> { impl<'d> OutputOpenDrain<'d> { /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level] and [Speed]. #[inline] - pub fn new(pin: impl Peripheral

+ 'd, initial_output: Level, speed: Speed) -> Self { + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level, speed: Speed) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -454,7 +453,7 @@ impl<'d> OutputOpenDrain<'d> { /// and [Pull]. #[inline] #[cfg(gpio_v2)] - pub fn new_pull(pin: impl Peripheral

+ 'd, initial_output: Level, speed: Speed, pull: Pull) -> Self { + pub fn new_pull(pin: Peri<'d, impl Pin>, initial_output: Level, speed: Speed, pull: Pull) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -539,16 +538,16 @@ impl OutputType { #[cfg(gpio_v1)] const fn to_cnf_out(self) -> vals::CnfOut { match self { - OutputType::PushPull => vals::CnfOut::ALTPUSHPULL, - OutputType::OpenDrain => vals::CnfOut::ALTOPENDRAIN, + OutputType::PushPull => vals::CnfOut::ALT_PUSH_PULL, + OutputType::OpenDrain => vals::CnfOut::ALT_OPEN_DRAIN, } } #[cfg(gpio_v2)] const fn to_ot(self) -> vals::Ot { match self { - OutputType::PushPull => vals::Ot::PUSHPULL, - OutputType::OpenDrain => vals::Ot::OPENDRAIN, + OutputType::PushPull => vals::Ot::PUSH_PULL, + OutputType::OpenDrain => vals::Ot::OPEN_DRAIN, } } } @@ -624,8 +623,8 @@ impl AfType { pub const fn input(pull: Pull) -> Self { Self { pupdr: pull.to_pupdr(), - ot: vals::Ot::PUSHPULL, - ospeedr: vals::Ospeedr::LOWSPEED, + ot: vals::Ot::PUSH_PULL, + ospeedr: vals::Ospeedr::LOW_SPEED, } } @@ -658,6 +657,16 @@ fn set_as_af(pin_port: u8, af_num: u8, af_type: AfType) { r.moder().modify(|w| w.set_moder(n, vals::Moder::ALTERNATE)); } +#[inline(never)] +#[cfg(gpio_v2)] +fn set_speed(pin_port: u8, speed: Speed) { + let pin = unsafe { AnyPin::steal(pin_port) }; + let r = pin.block(); + let n = pin._pin() as usize; + + r.ospeedr().modify(|w| w.set_ospeedr(n, speed.to_ospeedr())); +} + #[inline(never)] fn set_as_analog(pin_port: u8) { let pin = unsafe { AnyPin::steal(pin_port) }; @@ -695,8 +704,8 @@ fn get_pull(pin_port: u8) -> Pull { #[cfg(gpio_v2)] return match r.pupdr().read().pupdr(n) { vals::Pupdr::FLOATING => Pull::None, - vals::Pupdr::PULLDOWN => Pull::Down, - vals::Pupdr::PULLUP => Pull::Up, + vals::Pupdr::PULL_DOWN => Pull::Down, + vals::Pupdr::PULL_UP => Pull::Up, vals::Pupdr::_RESERVED_3 => Pull::None, }; } @@ -716,7 +725,7 @@ pub(crate) trait SealedPin { #[inline] fn block(&self) -> gpio::Gpio { - pac::GPIO(self._port() as _) + crate::_generated::gpio_block(self._port() as _) } /// Set the output as high. @@ -738,6 +747,12 @@ pub(crate) trait SealedPin { set_as_af(self.pin_port(), af_num, af_type) } + #[inline] + #[cfg(gpio_v2)] + fn set_speed(&self, speed: Speed) { + set_speed(self.pin_port(), speed) + } + #[inline] fn set_as_analog(&self) { set_as_analog(self.pin_port()); @@ -764,7 +779,7 @@ pub(crate) trait SealedPin { /// GPIO pin trait. #[allow(private_bounds)] -pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static { +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { /// EXTI channel assigned to this pin. /// /// For example, PC4 uses EXTI4. @@ -782,18 +797,6 @@ pub trait Pin: Peripheral

+ Into + SealedPin + Sized + 'static fn port(&self) -> u8 { self._port() } - - /// 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] - fn degrade(self) -> AnyPin { - AnyPin { - pin_port: self.pin_port(), - } - } } /// Type-erased GPIO pin @@ -806,8 +809,8 @@ impl AnyPin { /// /// `pin_port` is `port_num * 16 + pin_num`, where `port_num` is 0 for port `A`, 1 for port `B`, etc... #[inline] - pub unsafe fn steal(pin_port: u8) -> Self { - Self { pin_port } + pub unsafe fn steal(pin_port: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_port }) } #[inline] @@ -819,7 +822,7 @@ impl AnyPin { #[cfg(feature = "unstable-pac")] #[inline] pub fn block(&self) -> gpio::Gpio { - pac::GPIO(self._port() as _) + crate::_generated::gpio_block(self._port() as _) } } @@ -851,8 +854,10 @@ foreach_pin!( } impl From for AnyPin { - fn from(x: peripherals::$pin_name) -> Self { - x.degrade() + fn from(val: peripherals::$pin_name) -> Self { + Self { + pin_port: val.pin_port(), + } } } }; diff --git a/embassy-stm32/src/hash/mod.rs b/embassy-stm32/src/hash/mod.rs index 4d4a8ec5b..e62151bb5 100644 --- a/embassy-stm32/src/hash/mod.rs +++ b/embassy-stm32/src/hash/mod.rs @@ -8,16 +8,18 @@ use core::ptr; #[cfg(hash_v2)] use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use stm32_metapac::hash::regs::*; -use crate::dma::NoDma; #[cfg(hash_v2)] -use crate::dma::Transfer; +use crate::dma::ChannelAndRequest; use crate::interrupt::typelevel::Interrupt; +#[cfg(hash_v2)] +use crate::mode::Async; +use crate::mode::{Blocking, Mode}; use crate::peripherals::HASH; -use crate::{interrupt, pac, peripherals, rcc, Peripheral}; +use crate::{interrupt, pac, peripherals, rcc, Peri}; #[cfg(hash_v1)] const NUM_CONTEXT_REGS: usize = 51; @@ -99,6 +101,7 @@ pub enum DataType { /// Stores the state of the HASH peripheral for suspending/resuming /// digest calculation. +#[derive(Clone)] pub struct Context<'c> { first_word_sent: bool, key_sent: bool, @@ -116,24 +119,25 @@ pub struct Context<'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>, +pub struct Hash<'d, T: Instance, M: Mode> { + _peripheral: Peri<'d, T>, + _phantom: PhantomData, + #[cfg(hash_v2)] + dma: Option>, } -impl<'d, T: Instance, D> Hash<'d, T, D> { +impl<'d, T: Instance> Hash<'d, T, Blocking> { /// Instantiates, resets, and enables the HASH peripheral. - pub fn new( - peripheral: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, + pub fn new_blocking( + peripheral: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { rcc::enable_and_reset::(); - into_ref!(peripheral, dma); let instance = Self { _peripheral: peripheral, - dma: dma, + _phantom: PhantomData, + #[cfg(hash_v2)] + dma: None, }; T::Interrupt::unpend(); @@ -141,7 +145,9 @@ impl<'d, T: Instance, D> Hash<'d, T, D> { instance } +} +impl<'d, T: Instance, M: Mode> Hash<'d, T, M> { /// 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. @@ -282,14 +288,135 @@ impl<'d, T: Instance, D> Hash<'d, T, D> { 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 + } + + /// 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; + } + } + + /// 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; + } + } +} + +#[cfg(hash_v2)] +impl<'d, T: Instance> Hash<'d, T, Async> { + /// Instantiates, resets, and enables the HASH peripheral. + pub fn new( + peripheral: Peri<'d, T>, + dma: Peri<'d, impl Dma>, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + rcc::enable_and_reset::(); + let instance = Self { + _peripheral: peripheral, + _phantom: PhantomData, + dma: new_dma!(dma), + }; + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + instance + } + /// 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, - { + pub async fn update(&mut self, ctx: &mut Context<'_>, input: &[u8]) { // Restore the peripheral state. self.load_context(&ctx); @@ -353,68 +480,7 @@ impl<'d, T: Instance, D> Hash<'d, T, D> { /// 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, - { + pub async fn finish<'c>(&mut self, mut ctx: Context<'c>, digest: &mut [u8]) -> usize { // Restore the peripheral state. self.load_context(&ctx); @@ -483,27 +549,7 @@ impl<'d, T: Instance, D> Hash<'d, T, D> { } /// 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, - { + async fn accumulate(&mut self, input: &[u8]) { // Ignore an input length of 0. if input.len() == 0 { return; @@ -514,50 +560,20 @@ impl<'d, T: Instance, D> Hash<'d, T, D> { 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 dst_ptr: *mut u32 = 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()) }; + let src_ptr: *const [u8] = ptr::slice_from_raw_parts(input.as_ptr().cast(), num_words); + + let dma = self.dma.as_mut().unwrap(); + let dma_transfer = unsafe { dma.write_raw(src_ptr, dst_ptr as *mut u32, 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; - } - } } trait SealedInstance { @@ -566,7 +582,7 @@ trait SealedInstance { /// HASH instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + crate::rcc::RccPeripheral + 'static + Send { /// Interrupt for this HASH instance. type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index 13343fc2a..1d0594125 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -4,12 +4,12 @@ mod traits; use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::Peri; pub use traits::Instance; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::rcc; use crate::time::Hertz; -use crate::{rcc, Peripheral}; /// HRTIM burst controller instance. pub struct BurstController { @@ -62,13 +62,13 @@ pub trait AdvancedChannel: SealedAdvancedChannel {} /// HRTIM PWM pin. pub struct PwmPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, + _pin: Peri<'d, AnyPin>, phantom: PhantomData<(T, C)>, } /// HRTIM complementary PWM pin. pub struct ComplementaryPwmPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, + _pin: Peri<'d, AnyPin>, phantom: PhantomData<(T, C)>, } @@ -76,8 +76,7 @@ macro_rules! advanced_channel_impl { ($new_chx:ident, $channel:tt, $ch_num:expr, $pin_trait:ident, $complementary_pin_trait:ident) => { impl<'d, T: Instance> PwmPin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { - into_ref!(pin); + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>) -> Self { critical_section::with(|_| { pin.set_low(); pin.set_as_af( @@ -86,7 +85,7 @@ macro_rules! advanced_channel_impl { ); }); PwmPin { - _pin: pin.map_into(), + _pin: pin.into(), phantom: PhantomData, } } @@ -94,8 +93,7 @@ macro_rules! advanced_channel_impl { impl<'d, T: Instance> ComplementaryPwmPin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { - into_ref!(pin); + pub fn $new_chx(pin: Peri<'d, impl $complementary_pin_trait>) -> Self { critical_section::with(|_| { pin.set_low(); pin.set_as_af( @@ -104,7 +102,7 @@ macro_rules! advanced_channel_impl { ); }); ComplementaryPwmPin { - _pin: pin.map_into(), + _pin: pin.into(), phantom: PhantomData, } } @@ -129,7 +127,7 @@ advanced_channel_impl!(new_chf, ChF, 5, ChannelFPin, ChannelFComplementaryPin); /// Struct used to divide a high resolution timer into multiple channels pub struct AdvancedPwm<'d, T: Instance> { - _inner: PeripheralRef<'d, T>, + _inner: Peri<'d, T>, /// Master instance. pub master: Master, /// Burst controller. @@ -154,7 +152,7 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { /// /// This splits the HRTIM into its constituent parts, which you can then use individually. pub fn new( - tim: impl Peripheral

+ 'd, + tim: Peri<'d, T>, _cha: Option>>, _chan: Option>>, _chb: Option>>, @@ -171,9 +169,7 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { Self::new_inner(tim) } - fn new_inner(tim: impl Peripheral

+ 'd) -> Self { - into_ref!(tim); - + fn new_inner(tim: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); #[cfg(stm32f334)] @@ -236,8 +232,6 @@ pub struct BridgeConverter> { impl> BridgeConverter { /// Create a new HRTIM bridge converter driver. pub fn new(_channel: C, frequency: Hertz) -> Self { - use crate::pac::hrtim::vals::{Activeeffect, Inactiveeffect}; - T::set_channel_frequency(C::raw(), frequency); // Always enable preload @@ -258,28 +252,16 @@ impl> BridgeConverter { // Therefore, software-implemented dead time must be used when setting the duty cycles // Set output 1 to active on a period event - T::regs() - .tim(C::raw()) - .setr(0) - .modify(|w| w.set_per(Activeeffect::SETACTIVE)); + T::regs().tim(C::raw()).setr(0).modify(|w| w.set_per(true)); // Set output 1 to inactive on a compare 1 event - T::regs() - .tim(C::raw()) - .rstr(0) - .modify(|w| w.set_cmp(0, Inactiveeffect::SETINACTIVE)); + T::regs().tim(C::raw()).rstr(0).modify(|w| w.set_cmp(0, true)); // Set output 2 to active on a compare 2 event - T::regs() - .tim(C::raw()) - .setr(1) - .modify(|w| w.set_cmp(1, Activeeffect::SETACTIVE)); + T::regs().tim(C::raw()).setr(1).modify(|w| w.set_cmp(1, true)); // Set output 2 to inactive on a compare 3 event - T::regs() - .tim(C::raw()) - .rstr(1) - .modify(|w| w.set_cmp(2, Inactiveeffect::SETINACTIVE)); + T::regs().tim(C::raw()).rstr(1).modify(|w| w.set_cmp(2, true)); Self { timer: PhantomData, diff --git a/embassy-stm32/src/hrtim/traits.rs b/embassy-stm32/src/hrtim/traits.rs index 75f9971e2..6c0661146 100644 --- a/embassy-stm32/src/hrtim/traits.rs +++ b/embassy-stm32/src/hrtim/traits.rs @@ -1,3 +1,5 @@ +use embassy_hal_internal::PeripheralType; + use crate::rcc::RccPeripheral; use crate::time::Hertz; @@ -153,7 +155,7 @@ pub(crate) trait SealedInstance: RccPeripheral { /// HRTIM instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static {} +pub trait Instance: SealedInstance + PeripheralType + 'static {} foreach_interrupt! { ($inst:ident, hrtim, HRTIM, MASTER, $irq:ident) => { diff --git a/embassy-stm32/src/hsem/mod.rs b/embassy-stm32/src/hsem/mod.rs index 06ab7a9bc..31527bcdb 100644 --- a/embassy-stm32/src/hsem/mod.rs +++ b/embassy-stm32/src/hsem/mod.rs @@ -1,13 +1,14 @@ //! Hardware Semaphore (HSEM) +use embassy_hal_internal::PeripheralType; + +use crate::pac; +use crate::rcc::RccPeripheral; // TODO: This code works for all HSEM implemenations except for the STM32WBA52/4/5xx MCUs. // Those MCUs have a different HSEM implementation (Secure semaphore lock support, // Privileged / unprivileged semaphore lock support, Semaphore lock protection via semaphore attribute), // which is not yet supported by this code. -use embassy_hal_internal::{into_ref, PeripheralRef}; - -use crate::rcc::RccPeripheral; -use crate::{pac, Peripheral}; +use crate::Peri; /// HSEM error. #[derive(Debug)] @@ -73,13 +74,12 @@ fn core_id_to_index(core: CoreId) -> usize { /// HSEM driver pub struct HardwareSemaphore<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, + _peri: Peri<'d, T>, } impl<'d, T: Instance> HardwareSemaphore<'d, T> { /// Creates a new HardwareSemaphore instance. - pub fn new(peripheral: impl Peripheral

+ 'd) -> Self { - into_ref!(peripheral); + pub fn new(peripheral: Peri<'d, T>) -> Self { HardwareSemaphore { _peri: peripheral } } @@ -177,7 +177,7 @@ trait SealedInstance { /// HSEM instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + Send + 'static {} +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + Send + 'static {} impl SealedInstance for crate::peripherals::HSEM { fn regs() -> crate::pac::hsem::Hsem { diff --git a/embassy-stm32/src/hspi/enums.rs b/embassy-stm32/src/hspi/enums.rs new file mode 100644 index 000000000..83a88f4d9 --- /dev/null +++ b/embassy-stm32/src/hspi/enums.rs @@ -0,0 +1,422 @@ +//! Enums used in Hspi configuration. + +#[allow(dead_code)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) enum HspiMode { + IndirectWrite, + IndirectRead, + AutoPolling, + MemoryMapped, +} + +impl Into for HspiMode { + fn into(self) -> u8 { + match self { + HspiMode::IndirectWrite => 0b00, + HspiMode::IndirectRead => 0b01, + HspiMode::AutoPolling => 0b10, + HspiMode::MemoryMapped => 0b11, + } + } +} + +/// Hspi lane width +#[allow(dead_code)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum HspiWidth { + /// None + NONE, + /// Single lane + SING, + /// Dual lanes + DUAL, + /// Quad lanes + QUAD, + /// Eight lanes + OCTO, + /// Sixteen lanes + HEXADECA, +} + +impl Into for HspiWidth { + fn into(self) -> u8 { + match self { + HspiWidth::NONE => 0b00, + HspiWidth::SING => 0b01, + HspiWidth::DUAL => 0b10, + HspiWidth::QUAD => 0b11, + HspiWidth::OCTO => 0b100, + HspiWidth::HEXADECA => 0b101, + } + } +} + +/// Flash bank selection +#[allow(dead_code)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FlashSelection { + /// Bank 1 + Flash1, + /// Bank 2 + Flash2, +} + +impl Into for FlashSelection { + fn into(self) -> bool { + match self { + FlashSelection::Flash1 => false, + FlashSelection::Flash2 => true, + } + } +} + +/// Wrap Size +#[allow(dead_code)] +#[allow(missing_docs)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WrapSize { + None, + _16Bytes, + _32Bytes, + _64Bytes, + _128Bytes, +} + +impl Into for WrapSize { + fn into(self) -> u8 { + match self { + WrapSize::None => 0x00, + WrapSize::_16Bytes => 0x02, + WrapSize::_32Bytes => 0x03, + WrapSize::_64Bytes => 0x04, + WrapSize::_128Bytes => 0x05, + } + } +} + +/// Memory Type +#[allow(missing_docs)] +#[allow(dead_code)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MemoryType { + Micron, + Macronix, + Standard, + MacronixRam, + HyperBusMemory, + HyperBusRegister, +} + +impl Into for MemoryType { + fn into(self) -> u8 { + match self { + MemoryType::Micron => 0x00, + MemoryType::Macronix => 0x01, + MemoryType::Standard => 0x02, + MemoryType::MacronixRam => 0x03, + MemoryType::HyperBusMemory => 0x04, + MemoryType::HyperBusRegister => 0x04, + } + } +} + +/// Hspi memory size. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MemorySize { + _1KiB, + _2KiB, + _4KiB, + _8KiB, + _16KiB, + _32KiB, + _64KiB, + _128KiB, + _256KiB, + _512KiB, + _1MiB, + _2MiB, + _4MiB, + _8MiB, + _16MiB, + _32MiB, + _64MiB, + _128MiB, + _256MiB, + _512MiB, + _1GiB, + _2GiB, + _4GiB, + Other(u8), +} + +impl Into for MemorySize { + fn into(self) -> u8 { + match self { + MemorySize::_1KiB => 6, + MemorySize::_2KiB => 7, + MemorySize::_4KiB => 8, + MemorySize::_8KiB => 9, + MemorySize::_16KiB => 10, + MemorySize::_32KiB => 11, + MemorySize::_64KiB => 12, + MemorySize::_128KiB => 13, + MemorySize::_256KiB => 14, + MemorySize::_512KiB => 15, + MemorySize::_1MiB => 16, + MemorySize::_2MiB => 17, + MemorySize::_4MiB => 18, + MemorySize::_8MiB => 19, + MemorySize::_16MiB => 20, + MemorySize::_32MiB => 21, + MemorySize::_64MiB => 22, + MemorySize::_128MiB => 23, + MemorySize::_256MiB => 24, + MemorySize::_512MiB => 25, + MemorySize::_1GiB => 26, + MemorySize::_2GiB => 27, + MemorySize::_4GiB => 28, + MemorySize::Other(val) => val, + } + } +} + +/// Hspi Address size +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AddressSize { + /// 8-bit address + _8Bit, + /// 16-bit address + _16Bit, + /// 24-bit address + _24Bit, + /// 32-bit address + _32Bit, +} + +impl Into for AddressSize { + fn into(self) -> u8 { + match self { + AddressSize::_8Bit => 0b00, + AddressSize::_16Bit => 0b01, + AddressSize::_24Bit => 0b10, + AddressSize::_32Bit => 0b11, + } + } +} + +/// Time the Chip Select line stays high. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ChipSelectHighTime { + _1Cycle, + _2Cycle, + _3Cycle, + _4Cycle, + _5Cycle, + _6Cycle, + _7Cycle, + _8Cycle, +} + +impl Into for ChipSelectHighTime { + fn into(self) -> u8 { + match self { + ChipSelectHighTime::_1Cycle => 0, + ChipSelectHighTime::_2Cycle => 1, + ChipSelectHighTime::_3Cycle => 2, + ChipSelectHighTime::_4Cycle => 3, + ChipSelectHighTime::_5Cycle => 4, + ChipSelectHighTime::_6Cycle => 5, + ChipSelectHighTime::_7Cycle => 6, + ChipSelectHighTime::_8Cycle => 7, + } + } +} + +/// FIFO threshold. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FIFOThresholdLevel { + _1Bytes, + _2Bytes, + _3Bytes, + _4Bytes, + _5Bytes, + _6Bytes, + _7Bytes, + _8Bytes, + _9Bytes, + _10Bytes, + _11Bytes, + _12Bytes, + _13Bytes, + _14Bytes, + _15Bytes, + _16Bytes, + _17Bytes, + _18Bytes, + _19Bytes, + _20Bytes, + _21Bytes, + _22Bytes, + _23Bytes, + _24Bytes, + _25Bytes, + _26Bytes, + _27Bytes, + _28Bytes, + _29Bytes, + _30Bytes, + _31Bytes, + _32Bytes, +} + +impl Into for FIFOThresholdLevel { + fn into(self) -> u8 { + match self { + FIFOThresholdLevel::_1Bytes => 0, + FIFOThresholdLevel::_2Bytes => 1, + FIFOThresholdLevel::_3Bytes => 2, + FIFOThresholdLevel::_4Bytes => 3, + FIFOThresholdLevel::_5Bytes => 4, + FIFOThresholdLevel::_6Bytes => 5, + FIFOThresholdLevel::_7Bytes => 6, + FIFOThresholdLevel::_8Bytes => 7, + FIFOThresholdLevel::_9Bytes => 8, + FIFOThresholdLevel::_10Bytes => 9, + FIFOThresholdLevel::_11Bytes => 10, + FIFOThresholdLevel::_12Bytes => 11, + FIFOThresholdLevel::_13Bytes => 12, + FIFOThresholdLevel::_14Bytes => 13, + FIFOThresholdLevel::_15Bytes => 14, + FIFOThresholdLevel::_16Bytes => 15, + FIFOThresholdLevel::_17Bytes => 16, + FIFOThresholdLevel::_18Bytes => 17, + FIFOThresholdLevel::_19Bytes => 18, + FIFOThresholdLevel::_20Bytes => 19, + FIFOThresholdLevel::_21Bytes => 20, + FIFOThresholdLevel::_22Bytes => 21, + FIFOThresholdLevel::_23Bytes => 22, + FIFOThresholdLevel::_24Bytes => 23, + FIFOThresholdLevel::_25Bytes => 24, + FIFOThresholdLevel::_26Bytes => 25, + FIFOThresholdLevel::_27Bytes => 26, + FIFOThresholdLevel::_28Bytes => 27, + FIFOThresholdLevel::_29Bytes => 28, + FIFOThresholdLevel::_30Bytes => 29, + FIFOThresholdLevel::_31Bytes => 30, + FIFOThresholdLevel::_32Bytes => 31, + } + } +} + +/// Dummy cycle count +#[allow(missing_docs)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DummyCycles { + _0, + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, + _17, + _18, + _19, + _20, + _21, + _22, + _23, + _24, + _25, + _26, + _27, + _28, + _29, + _30, + _31, +} + +impl Into for DummyCycles { + fn into(self) -> u8 { + match self { + DummyCycles::_0 => 0, + DummyCycles::_1 => 1, + DummyCycles::_2 => 2, + DummyCycles::_3 => 3, + DummyCycles::_4 => 4, + DummyCycles::_5 => 5, + DummyCycles::_6 => 6, + DummyCycles::_7 => 7, + DummyCycles::_8 => 8, + DummyCycles::_9 => 9, + DummyCycles::_10 => 10, + DummyCycles::_11 => 11, + DummyCycles::_12 => 12, + DummyCycles::_13 => 13, + DummyCycles::_14 => 14, + DummyCycles::_15 => 15, + DummyCycles::_16 => 16, + DummyCycles::_17 => 17, + DummyCycles::_18 => 18, + DummyCycles::_19 => 19, + DummyCycles::_20 => 20, + DummyCycles::_21 => 21, + DummyCycles::_22 => 22, + DummyCycles::_23 => 23, + DummyCycles::_24 => 24, + DummyCycles::_25 => 25, + DummyCycles::_26 => 26, + DummyCycles::_27 => 27, + DummyCycles::_28 => 28, + DummyCycles::_29 => 29, + DummyCycles::_30 => 30, + DummyCycles::_31 => 31, + } + } +} + +/// Functional mode +#[allow(missing_docs)] +#[allow(dead_code)] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FunctionalMode { + IndirectWrite, + IndirectRead, + AutoStatusPolling, + MemoryMapped, +} + +impl Into for FunctionalMode { + fn into(self) -> u8 { + match self { + FunctionalMode::IndirectWrite => 0x00, + FunctionalMode::IndirectRead => 0x01, + FunctionalMode::AutoStatusPolling => 0x02, + FunctionalMode::MemoryMapped => 0x03, + } + } +} diff --git a/embassy-stm32/src/hspi/mod.rs b/embassy-stm32/src/hspi/mod.rs new file mode 100644 index 000000000..62bc0e979 --- /dev/null +++ b/embassy-stm32/src/hspi/mod.rs @@ -0,0 +1,1007 @@ +//! HSPI Serial Peripheral Interface +//! + +// NOTE: This is a partial implementation of the HSPI driver. +// It implements only Single and Octal SPI modes, but additional +// modes can be added as needed following the same pattern and +// using ospi/mod.rs as a reference. + +#![macro_use] + +pub mod enums; + +use core::marker::PhantomData; + +use embassy_embedded_hal::{GetConfig, SetConfig}; +use embassy_hal_internal::{Peri, PeripheralType}; +pub use enums::*; + +use crate::dma::{word, ChannelAndRequest}; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::pac::hspi::Hspi as Regs; +use crate::peripherals; +use crate::rcc::{self, RccPeripheral}; + +/// HSPI driver config. +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// Fifo threshold used by the peripheral to generate the interrupt indicating data + /// or space is available in the FIFO + pub fifo_threshold: FIFOThresholdLevel, + /// Indicates the type of external device connected + pub memory_type: MemoryType, // Need to add an additional enum to provide this public interface + /// Defines the size of the external device connected to the HSPI corresponding + /// to the number of address bits required to access the device + pub device_size: MemorySize, + /// Sets the minimum number of clock cycles that the chip select signal must be held high + /// between commands + pub chip_select_high_time: ChipSelectHighTime, + /// Enables the free running clock + pub free_running_clock: bool, + /// Sets the clock level when the device is not selected + pub clock_mode: bool, + /// Indicates the wrap size corresponding to the external device configuration + pub wrap_size: WrapSize, + /// Specified the prescaler factor used for generating the external clock based + /// on the AHB clock + pub clock_prescaler: u8, + /// Allows the delay of 1/2 cycle the data sampling to account for external + /// signal delays + pub sample_shifting: bool, + /// Allows hold to 1/4 cycle the data + pub delay_hold_quarter_cycle: bool, + /// Enables the transaction boundary feature and defines the boundary to release + /// the chip select + pub chip_select_boundary: u8, + /// Enables the delay block bypass so the sampling is not affected by the delay block + pub delay_block_bypass: bool, + /// Enables communication regulation feature. Chip select is released when the other + /// HSPI requests access to the bus + pub max_transfer: u8, + /// Enables the refresh feature, chip select is released every refresh + 1 clock cycles + pub refresh: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + fifo_threshold: FIFOThresholdLevel::_16Bytes, + memory_type: MemoryType::Micron, + device_size: MemorySize::Other(0), + chip_select_high_time: ChipSelectHighTime::_5Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 0, + sample_shifting: false, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, // Acceptable range 0 to 31 + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + } + } +} + +/// HSPI transfer configuration. +pub struct TransferConfig { + /// Instruction width (IMODE) + pub iwidth: HspiWidth, + /// Instruction Id + pub instruction: Option, + /// Number of Instruction Bytes + pub isize: AddressSize, + /// Instruction Double Transfer rate enable + pub idtr: bool, + + /// Address width (ADMODE) + pub adwidth: HspiWidth, + /// Device memory address + pub address: Option, + /// Number of Address Bytes + pub adsize: AddressSize, + /// Address Double Transfer rate enable + pub addtr: bool, + + /// Alternate bytes width (ABMODE) + pub abwidth: HspiWidth, + /// Alternate Bytes + pub alternate_bytes: Option, + /// Number of Alternate Bytes + pub absize: AddressSize, + /// Alternate Bytes Double Transfer rate enable + pub abdtr: bool, + + /// Data width (DMODE) + pub dwidth: HspiWidth, + /// Data buffer + pub ddtr: bool, + + /// Number of dummy cycles (DCYC) + pub dummy: DummyCycles, +} + +impl Default for TransferConfig { + fn default() -> Self { + Self { + iwidth: HspiWidth::NONE, + instruction: None, + isize: AddressSize::_8Bit, + idtr: false, + + adwidth: HspiWidth::NONE, + address: None, + adsize: AddressSize::_8Bit, + addtr: false, + + abwidth: HspiWidth::NONE, + alternate_bytes: None, + absize: AddressSize::_8Bit, + abdtr: false, + + dwidth: HspiWidth::NONE, + ddtr: false, + + dummy: DummyCycles::_0, + } + } +} + +/// Error used for HSPI implementation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum HspiError { + /// Peripheral configuration is invalid + InvalidConfiguration, + /// Operation configuration is invalid + InvalidCommand, + /// Size zero buffer passed to instruction + EmptyBuffer, +} + +/// HSPI driver. +pub struct Hspi<'d, T: Instance, M: PeriMode> { + _peri: Peri<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + d11: Option>, + d12: Option>, + d13: Option>, + d14: Option>, + d15: Option>, + nss: Option>, + dqs0: Option>, + dqs1: Option>, + dma: Option>, + _phantom: PhantomData, + config: Config, + width: HspiWidth, +} + +impl<'d, T: Instance, M: PeriMode> Hspi<'d, T, M> { + /// Enter memory mode. + /// The Input `read_config` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), HspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + + // Set writing configurations, there are separate registers for write configurations in memory mapped mode + T::REGS.wccr().modify(|w| { + w.set_imode(write_config.iwidth.into()); + w.set_idtr(write_config.idtr); + w.set_isize(write_config.isize.into()); + + w.set_admode(write_config.adwidth.into()); + w.set_addtr(write_config.idtr); + w.set_adsize(write_config.adsize.into()); + + w.set_dmode(write_config.dwidth.into()); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(write_config.abwidth.into()); + w.set_dqse(true); + }); + + T::REGS.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode + T::REGS.cr().modify(|r| { + r.set_fmode(FunctionalMode::MemoryMapped.into()); + r.set_tcen(false); + }); + Ok(()) + } + + /// Quit from memory mapped mode + pub fn disable_memory_mapped_mode(&mut self) { + T::REGS.cr().modify(|r| { + r.set_fmode(FunctionalMode::IndirectWrite.into()); + r.set_abort(true); + r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + T::REGS.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable HSPI + T::REGS.cr().modify(|r| { + r.set_en(true); + }); + } + + fn new_inner( + peri: Peri<'d, T>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + d11: Option>, + d12: Option>, + d13: Option>, + d14: Option>, + d15: Option>, + sck: Option>, + nss: Option>, + dqs0: Option>, + dqs1: Option>, + dma: Option>, + config: Config, + width: HspiWidth, + dual_memory_mode: bool, + ) -> Self { + // System configuration + rcc::enable_and_reset::(); + + // Call this function just to check that the clock for HSPI1 is properly setup + let _ = T::frequency(); + + while T::REGS.sr().read().busy() {} + + Self::configure_registers(&config, Some(dual_memory_mode)); + + Self { + _peri: peri, + sck, + d0, + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + d15, + nss, + dqs0, + dqs1, + dma, + _phantom: PhantomData, + config, + width, + } + } + + fn configure_registers(config: &Config, dual_memory_mode: Option) { + // Device configuration + T::REGS.dcr1().modify(|w| { + w.set_mtyp(config.memory_type.into()); + w.set_devsize(config.device_size.into()); + w.set_csht(config.chip_select_high_time.into()); + w.set_frck(false); + w.set_ckmode(config.clock_mode); + w.set_dlybyp(config.delay_block_bypass); + }); + + T::REGS.dcr2().modify(|w| { + w.set_wrapsize(config.wrap_size.into()); + }); + + T::REGS.dcr3().modify(|w| { + w.set_csbound(config.chip_select_boundary); + w.set_maxtran(config.max_transfer); + }); + + T::REGS.dcr4().modify(|w| { + w.set_refresh(config.refresh); + }); + + T::REGS.cr().modify(|w| { + w.set_fthres(config.fifo_threshold.into()); + }); + + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + T::REGS.dcr2().modify(|w| { + w.set_prescaler(config.clock_prescaler); + }); + + // The configuration of clock prescaler trigger automatically a calibration process + // So it is necessary to wait the calibration is complete + while T::REGS.sr().read().busy() {} + + if let Some(dual_memory_mode) = dual_memory_mode { + T::REGS.cr().modify(|w| { + w.set_dmm(dual_memory_mode); + }); + } + + T::REGS.tcr().modify(|w| { + w.set_sshift(config.sample_shifting); + w.set_dhqc(config.delay_hold_quarter_cycle); + }); + + // Enable peripheral + T::REGS.cr().modify(|w| { + w.set_en(true); + }); + + // Free running clock needs to be set after peripheral enable + if config.free_running_clock { + T::REGS.dcr1().modify(|w| { + w.set_frck(config.free_running_clock); + }); + } + } + + // Function to configure the peripheral for the requested command + fn configure_command(&mut self, command: &TransferConfig, data_len: Option) -> Result<(), HspiError> { + // Check that transaction doesn't use more than hardware initialized pins + if >::into(command.iwidth) > >::into(self.width) + || >::into(command.adwidth) > >::into(self.width) + || >::into(command.abwidth) > >::into(self.width) + || >::into(command.dwidth) > >::into(self.width) + { + return Err(HspiError::InvalidCommand); + } + + while T::REGS.sr().read().busy() {} + + T::REGS.cr().modify(|w| { + w.set_fmode(0.into()); + }); + + // Configure alternate bytes + if let Some(ab) = command.alternate_bytes { + T::REGS.abr().write(|v| v.set_alternate(ab)); + T::REGS.ccr().modify(|w| { + w.set_abmode(command.abwidth.into()); + w.set_abdtr(command.abdtr); + w.set_absize(command.absize.into()); + }) + } + + // Configure dummy cycles + T::REGS.tcr().modify(|w| { + w.set_dcyc(command.dummy.into()); + }); + + // Configure data + if let Some(data_length) = data_len { + T::REGS.dlr().write(|v| { + v.set_dl((data_length - 1) as u32); + }) + } else { + T::REGS.dlr().write(|v| { + v.set_dl((0) as u32); + }) + } + + // Configure instruction/address/data modes + T::REGS.ccr().modify(|w| { + w.set_imode(command.iwidth.into()); + w.set_idtr(command.idtr); + w.set_isize(command.isize.into()); + + w.set_admode(command.adwidth.into()); + w.set_addtr(command.addtr); + w.set_adsize(command.adsize.into()); + + w.set_dmode(command.dwidth.into()); + w.set_ddtr(command.ddtr); + }); + + // Configure DQS + T::REGS.ccr().modify(|w| { + w.set_dqse(command.ddtr && command.instruction.unwrap_or(0) != 0x12ED); + }); + + // Set information required to initiate transaction + if let Some(instruction) = command.instruction { + if let Some(address) = command.address { + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + } + } else { + if let Some(address) = command.address { + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + // The only single phase transaction supported is instruction only + return Err(HspiError::InvalidCommand); + } + } + + Ok(()) + } + + /// Function used to control or configure the target device without data transfer + pub fn blocking_command(&mut self, command: &TransferConfig) -> Result<(), HspiError> { + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Need additional validation that command configuration doesn't have data set + self.configure_command(command, None)?; + + // Transaction initiated by setting final configuration, i.e the instruction register + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|w| { + w.set_ctcf(true); + }); + + Ok(()) + } + + /// Blocking read with byte by byte data transfer + pub fn blocking_read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Ensure DMA is not enabled for this transaction + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectRead.into())); + if T::REGS.ccr().read().admode() == HspiWidth::NONE.into() { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + for idx in 0..buf.len() { + while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + buf[idx] = unsafe { (T::REGS.dr().as_ptr() as *mut W).read_volatile() }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Blocking write with byte by byte data transfer + pub fn blocking_write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + self.configure_command(&transaction, Some(buf.len()))?; + + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); + + for idx in 0..buf.len() { + while !T::REGS.sr().read().ftf() {} + unsafe { (T::REGS.dr().as_ptr() as *mut W).write_volatile(buf[idx]) }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Set new bus configuration + pub fn set_config(&mut self, config: &Config) { + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + // Disable DMA channel while configuring the peripheral + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + Self::configure_registers(config, None); + + self.config = *config; + } + + /// Get current configuration + pub fn get_config(&self) -> Config { + self.config + } +} + +impl<'d, T: Instance> Hspi<'d, T, Blocking> { + /// Create new blocking HSPI driver for single spi external chip + pub fn new_blocking_singlespi( + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + nss: Peri<'d, impl NSSPin>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + config, + HspiWidth::SING, + false, + ) + } + + /// Create new blocking HSPI driver for octospi external chip + pub fn new_blocking_octospi( + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + nss: Peri<'d, impl NSSPin>, + dqs0: Peri<'d, impl DQS0Pin>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + new_pin!(dqs0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + config, + HspiWidth::OCTO, + false, + ) + } +} + +impl<'d, T: Instance> Hspi<'d, T, Async> { + /// Create new HSPI driver for a single spi external chip + pub fn new_singlespi( + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + nss: Peri<'d, impl NSSPin>, + dma: Peri<'d, impl HspiDma>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + new_dma!(dma), + config, + HspiWidth::SING, + false, + ) + } + + /// Create new HSPI driver for octospi external chip + pub fn new_octospi( + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + nss: Peri<'d, impl NSSPin>, + dqs0: Peri<'d, impl DQS0Pin>, + dma: Peri<'d, impl HspiDma>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(sck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!( + nss, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + new_pin!(dqs0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + new_dma!(dma), + config, + HspiWidth::OCTO, + false, + ) + } + + /// Blocking read with DMA transfer + pub fn blocking_read_dma(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectRead.into())); + if T::REGS.ccr().read().admode() == HspiWidth::NONE.into() { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Blocking write with DMA transfer + pub fn blocking_write_dma(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous read from external device + pub async fn read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectRead.into())); + if T::REGS.ccr().read().admode() == HspiWidth::NONE.into() { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous write to external device + pub async fn write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), HspiError> { + if buf.is_empty() { + return Err(HspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(FunctionalMode::IndirectWrite.into())); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> Drop for Hspi<'d, T, M> { + fn drop(&mut self) { + self.sck.as_ref().map(|x| x.set_as_disconnected()); + self.d0.as_ref().map(|x| x.set_as_disconnected()); + self.d1.as_ref().map(|x| x.set_as_disconnected()); + self.d2.as_ref().map(|x| x.set_as_disconnected()); + self.d3.as_ref().map(|x| x.set_as_disconnected()); + self.d4.as_ref().map(|x| x.set_as_disconnected()); + self.d5.as_ref().map(|x| x.set_as_disconnected()); + self.d6.as_ref().map(|x| x.set_as_disconnected()); + self.d7.as_ref().map(|x| x.set_as_disconnected()); + self.d8.as_ref().map(|x| x.set_as_disconnected()); + self.d9.as_ref().map(|x| x.set_as_disconnected()); + self.d10.as_ref().map(|x| x.set_as_disconnected()); + self.d11.as_ref().map(|x| x.set_as_disconnected()); + self.d12.as_ref().map(|x| x.set_as_disconnected()); + self.d13.as_ref().map(|x| x.set_as_disconnected()); + self.d14.as_ref().map(|x| x.set_as_disconnected()); + self.d15.as_ref().map(|x| x.set_as_disconnected()); + self.nss.as_ref().map(|x| x.set_as_disconnected()); + self.dqs0.as_ref().map(|x| x.set_as_disconnected()); + self.dqs1.as_ref().map(|x| x.set_as_disconnected()); + + rcc::disable::(); + } +} + +fn finish_dma(regs: Regs) { + while !regs.sr().read().tcf() {} + regs.fcr().write(|v| v.set_ctcf(true)); + + regs.cr().modify(|w| { + w.set_dmaen(false); + }); +} + +/// HSPI instance trait. +pub(crate) trait SealedInstance { + const REGS: Regs; +} + +/// HSPI instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral {} + +pin_trait!(SckPin, Instance); +pin_trait!(NckPin, Instance); +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(D4Pin, Instance); +pin_trait!(D5Pin, Instance); +pin_trait!(D6Pin, Instance); +pin_trait!(D7Pin, Instance); +pin_trait!(D8Pin, Instance); +pin_trait!(D9Pin, Instance); +pin_trait!(D10Pin, Instance); +pin_trait!(D11Pin, Instance); +pin_trait!(D12Pin, Instance); +pin_trait!(D13Pin, Instance); +pin_trait!(D14Pin, Instance); +pin_trait!(D15Pin, Instance); +pin_trait!(DQS0Pin, Instance); +pin_trait!(DQS1Pin, Instance); +pin_trait!(NSSPin, Instance); +dma_trait!(HspiDma, Instance); + +foreach_peripheral!( + (hspi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +impl<'d, T: Instance, M: PeriMode> SetConfig for Hspi<'d, T, M> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.set_config(config); + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> GetConfig for Hspi<'d, T, M> { + type Config = Config; + fn get_config(&self) -> Self::Config { + self.get_config() + } +} + +/// Word sizes usable for HSPI. +#[allow(private_bounds)] +pub trait Word: word::Word {} + +macro_rules! impl_word { + ($T:ty) => { + impl Word for $T {} + }; +} + +impl_word!(u8); +impl_word!(u16); +impl_word!(u32); diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 2ff21702b..825dd240c 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -12,7 +12,7 @@ use core::iter; use core::marker::PhantomData; pub use config::*; -use embassy_hal_internal::{Peripheral, PeripheralRef}; +use embassy_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; #[cfg(feature = "time")] use embassy_time::{Duration, Instant}; @@ -47,6 +47,24 @@ pub enum Error { ZeroLengthTransfer, } +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let message = match self { + Self::Bus => "Bus Error", + Self::Arbitration => "Arbitration Lost", + Self::Nack => "ACK Not Received", + Self::Timeout => "Request Timed Out", + Self::Crc => "CRC Mismatch", + Self::Overrun => "Buffer Overrun", + Self::ZeroLengthTransfer => "Zero-Length Transfers are not allowed", + }; + + write!(f, "{}", message) + } +} + +impl core::error::Error for Error {} + /// I2C modes pub mod mode { trait SealedMode {} @@ -99,8 +117,8 @@ pub enum SendStatus { struct I2CDropGuard<'d> { info: &'static Info, - scl: Option>, - sda: Option>, + scl: Option>, + sda: Option>, } impl<'d> Drop for I2CDropGuard<'d> { fn drop(&mut self) { @@ -132,14 +150,14 @@ pub struct I2c<'d, M: Mode, IM: MasterMode> { impl<'d> I2c<'d, Async, Master> { /// Create a new I2C driver. pub fn new( - peri: impl Peripheral

+ 'd, - scl: impl Peripheral

> + 'd, - sda: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, _irq: impl interrupt::typelevel::Binding> + interrupt::typelevel::Binding> + 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, freq: Hertz, config: Config, ) -> Self { @@ -158,9 +176,9 @@ impl<'d> I2c<'d, Async, Master> { impl<'d> I2c<'d, Blocking, Master> { /// Create a new blocking I2C driver. pub fn new_blocking( - peri: impl Peripheral

+ 'd, - scl: impl Peripheral

> + 'd, - sda: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, freq: Hertz, config: Config, ) -> Self { @@ -179,9 +197,9 @@ impl<'d> I2c<'d, Blocking, Master> { impl<'d, M: Mode> I2c<'d, M, Master> { /// Create a new I2C driver. fn new_inner( - _peri: impl Peripheral

+ 'd, - scl: Option>, - sda: Option>, + _peri: Peri<'d, T>, + scl: Option>, + sda: Option>, tx_dma: Option>, rx_dma: Option>, freq: Hertz, diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 64ccd24c7..990ce7d21 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -40,7 +40,7 @@ pub(crate) unsafe fn on_interrupt() { let regs = T::info().regs; let isr = regs.isr().read(); - if isr.tcr() || isr.tc() || isr.addr() || isr.stopf() { + if isr.tcr() || isr.tc() || isr.addr() || isr.stopf() || isr.nackf() || isr.berr() || isr.arlo() || isr.ovr() { T::state().waker.wake(); } @@ -48,10 +48,11 @@ pub(crate) unsafe fn on_interrupt() { regs.cr1().modify(|w| { w.set_addrie(false); w.set_stopie(false); + // The flag can only be cleared by writting to nbytes, we won't do that here w.set_tcie(false); - // The flag can only be cleared by writting to nbytes, we won't do that here, so disable - // the interrupt - w.set_tcie(false); + // Error flags are to be read in the routines, so we also don't clear them here + w.set_nackie(false); + w.set_errie(false); }); }); } @@ -107,7 +108,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { // is BUSY or I2C is in slave mode. let reload = if reload { - i2c::vals::Reload::NOTCOMPLETED + i2c::vals::Reload::NOT_COMPLETED } else { i2c::vals::Reload::COMPLETED }; @@ -148,7 +149,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } let reload = if reload { - i2c::vals::Reload::NOTCOMPLETED + i2c::vals::Reload::NOT_COMPLETED } else { i2c::vals::Reload::COMPLETED }; @@ -177,7 +178,7 @@ impl<'d, M: Mode, IM: MasterMode> I2c<'d, M, IM> { } let will_reload = if will_reload { - i2c::vals::Reload::NOTCOMPLETED + i2c::vals::Reload::NOT_COMPLETED } else { i2c::vals::Reload::COMPLETED }; @@ -483,6 +484,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { write: &[u8], first_slice: bool, last_slice: bool, + send_stop: bool, timeout: Timeout, ) -> Result<(), Error> { let total_len = write.len(); @@ -494,6 +496,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { if first_slice { w.set_tcie(true); } + w.set_nackie(true); + w.set_errie(true); }); let dst = regs.txdr().as_ptr() as *mut u8; @@ -504,18 +508,41 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { let on_drop = OnDrop::new(|| { let regs = self.info.regs; + let isr = regs.isr().read(); regs.cr1().modify(|w| { - if last_slice { + if last_slice || isr.nackf() || isr.arlo() || isr.berr() || isr.ovr() { w.set_txdmaen(false); } w.set_tcie(false); - }) + w.set_nackie(false); + w.set_errie(false); + }); + regs.icr().write(|w| { + w.set_nackcf(true); + w.set_berrcf(true); + w.set_arlocf(true); + w.set_ovrcf(true); + }); }); poll_fn(|cx| { self.state.waker.register(cx.waker()); let isr = self.info.regs.isr().read(); + + if isr.nackf() { + return Poll::Ready(Err(Error::Nack)); + } + if isr.arlo() { + return Poll::Ready(Err(Error::Arbitration)); + } + if isr.berr() { + return Poll::Ready(Err(Error::Bus)); + } + if isr.ovr() { + return Poll::Ready(Err(Error::Overrun)); + } + if remaining_len == total_len { if first_slice { Self::master_write( @@ -550,10 +577,12 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { .await?; dma_transfer.await; - if last_slice { // This should be done already self.wait_tc(timeout)?; + } + + if last_slice & send_stop { self.master_stop(); } @@ -576,6 +605,8 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { regs.cr1().modify(|w| { w.set_rxdmaen(true); w.set_tcie(true); + w.set_nackie(true); + w.set_errie(true); }); let src = regs.rxdr().as_ptr() as *mut u8; @@ -589,34 +620,62 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { regs.cr1().modify(|w| { w.set_rxdmaen(false); w.set_tcie(false); - }) + w.set_nackie(false); + w.set_errie(false); + }); + regs.icr().write(|w| { + w.set_nackcf(true); + w.set_berrcf(true); + w.set_arlocf(true); + w.set_ovrcf(true); + }); }); poll_fn(|cx| { self.state.waker.register(cx.waker()); let isr = self.info.regs.isr().read(); + + if isr.nackf() { + return Poll::Ready(Err(Error::Nack)); + } + if isr.arlo() { + return Poll::Ready(Err(Error::Arbitration)); + } + if isr.berr() { + return Poll::Ready(Err(Error::Bus)); + } + if isr.ovr() { + return Poll::Ready(Err(Error::Overrun)); + } + if remaining_len == total_len { Self::master_read( self.info, address, total_len.min(255), - Stop::Software, + Stop::Automatic, total_len > 255, restart, timeout, )?; - } else if !(isr.tcr() || isr.tc()) { + if total_len <= 255 { + return Poll::Ready(Ok(())); + } + } else if isr.tcr() { // poll_fn was woken without an interrupt present return Poll::Pending; - } else if remaining_len == 0 { - return Poll::Ready(Ok(())); } else { let last_piece = remaining_len <= 255; if let Err(e) = Self::reload(self.info, remaining_len.min(255), !last_piece, timeout) { return Poll::Ready(Err(e)); } + // Return here if we are on last chunk, + // end of transfer will be awaited with the DMA below + if last_piece { + return Poll::Ready(Ok(())); + } self.info.regs.cr1().modify(|w| w.set_tcie(true)); } @@ -626,11 +685,6 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { .await?; dma_transfer.await; - - // This should be done already - self.wait_tc(timeout)?; - self.master_stop(); - drop(on_drop); Ok(()) @@ -645,7 +699,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { self.write_internal(address.into(), write, true, timeout) } else { timeout - .with(self.write_dma_internal(address.into(), write, true, true, timeout)) + .with(self.write_dma_internal(address.into(), write, true, true, true, timeout)) .await } } @@ -667,7 +721,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { let next = iter.next(); let is_last = next.is_none(); - let fut = self.write_dma_internal(address, c, first, is_last, timeout); + let fut = self.write_dma_internal(address, c, first, is_last, is_last, timeout); timeout.with(fut).await?; first = false; current = next; @@ -694,7 +748,7 @@ impl<'d, IM: MasterMode> I2c<'d, Async, IM> { if write.is_empty() { self.write_internal(address.into(), write, false, timeout)?; } else { - let fut = self.write_dma_internal(address.into(), write, true, true, timeout); + let fut = self.write_dma_internal(address.into(), write, true, true, false, timeout); timeout.with(fut).await?; } @@ -1202,7 +1256,12 @@ impl<'d, M: Mode> SetConfig for I2c<'d, M, Master> { type Config = Hertz; type ConfigError = (); fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.info.regs.cr1().modify(|reg| { + reg.set_pe(false); + }); + let timings = Timings::new(self.kernel_clock, *config); + self.info.regs.timingr().write(|reg| { reg.set_presc(timings.prescale); reg.set_scll(timings.scll); @@ -1211,6 +1270,10 @@ impl<'d, M: Mode> SetConfig for I2c<'d, M, Master> { reg.set_scldel(timings.scldel); }); + self.info.regs.cr1().modify(|reg| { + reg.set_pe(true); + }); + Ok(()) } } @@ -1232,3 +1295,4 @@ impl<'d, M: Mode> SetConfig for I2c<'d, M, MultiMaster> { Ok(()) } } + diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs index 094de2461..5005a5cdb 100644 --- a/embassy-stm32/src/i2s.rs +++ b/embassy-stm32/src/i2s.rs @@ -1,14 +1,14 @@ //! Inter-IC Sound (I2S) -use embassy_hal_internal::into_ref; +use embassy_futures::join::join; +use stm32_metapac::spi::vals; -use crate::dma::ChannelAndRequest; +use crate::dma::{ringbuffer, ChannelAndRequest, ReadableRingBuffer, TransferOptions, WritableRingBuffer}; use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; use crate::mode::Async; -use crate::pac::spi::vals; -use crate::spi::{Config as SpiConfig, *}; +use crate::spi::{Config as SpiConfig, RegsExt as _, *}; use crate::time::Hertz; -use crate::{Peripheral, PeripheralRef}; +use crate::Peri; /// I2S mode #[derive(Copy, Clone)] @@ -19,6 +19,19 @@ pub enum Mode { Slave, } +/// I2S function +#[derive(Copy, Clone)] +#[allow(dead_code)] +enum Function { + /// Transmit audio data + Transmit, + /// Receive audio data + Receive, + #[cfg(spi_v3)] + /// Transmit and Receive audio data + FullDuplex, +} + /// I2C standard #[derive(Copy, Clone)] pub enum Standard { @@ -34,6 +47,30 @@ pub enum Standard { PcmShortSync, } +/// SAI error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// `write` called on a SAI in receive mode. + NotATransmitter, + /// `read` called on a SAI in transmit mode. + NotAReceiver, + /// Overrun + Overrun, +} + +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } + Self::Overrun + } +} + impl Standard { #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn i2sstd(&self) -> vals::I2sstd { @@ -103,8 +140,8 @@ impl ClockPolarity { #[cfg(any(spi_v1, spi_v3, spi_f1))] const fn ckpol(&self) -> vals::Ckpol { match self { - ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH, - ClockPolarity::IdleLow => vals::Ckpol::IDLELOW, + ClockPolarity::IdleHigh => vals::Ckpol::IDLE_HIGH, + ClockPolarity::IdleLow => vals::Ckpol::IDLE_LOW, } } } @@ -142,50 +179,81 @@ impl Default for Config { } } +/// I2S driver writer. Useful for moving write functionality across tasks. +pub struct Writer<'s, 'd, W: Word>(&'s mut WritableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Writer<'s, 'd, W> { + /// Write data to the I2S ringbuffer. + /// This appends the data to the buffer and returns immediately. The data will be transmitted in the background. + /// If thfre’s no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + self.0.write_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to recover from overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + +/// I2S driver reader. Useful for moving read functionality across tasks. +pub struct Reader<'s, 'd, W: Word>(&'s mut ReadableRingBuffer<'d, W>); + +impl<'s, 'd, W: Word> Reader<'s, 'd, W> { + /// Read data from the I2S ringbuffer. + /// SAI is always receiving data in the background. This function pops already-received data from the buffer. + /// If there’s less than data.len() data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + self.0.read_exact(data).await?; + Ok(()) + } + + /// Reset the ring buffer to its initial state. + /// Can be used to prevent overrun. + /// The ringbuffer will always auto-reset on Overrun in any case. + pub fn reset(&mut self) { + self.0.clear(); + } +} + /// I2S driver. -pub struct I2S<'d> { - _peri: Spi<'d, Async>, - txsd: Option>, - rxsd: Option>, - ws: Option>, - ck: Option>, - mck: Option>, +pub struct I2S<'d, W: Word> { + #[allow(dead_code)] + mode: Mode, + spi: Spi<'d, Async>, + txsd: Option>, + rxsd: Option>, + ws: Option>, + ck: Option>, + mck: Option>, + tx_ring_buffer: Option>, + rx_ring_buffer: Option>, } -/// I2S function -#[derive(Copy, Clone)] -#[allow(dead_code)] -enum Function { - /// Transmit audio data - Transmit, - /// Receive audio data - Receive, - #[cfg(spi_v3)] - /// Transmit and Receive audio data - FullDuplex, -} - -impl<'d> I2S<'d> { - /// Create a transmitter driver +impl<'d, W: Word> I2S<'d, W> { + /// Create a transmitter driver. pub fn new_txonly( - peri: impl Peripheral

+ 'd, - sd: impl Peripheral

> + 'd, - ws: impl Peripheral

> + 'd, - ck: impl Peripheral

> + 'd, - mck: impl Peripheral

> + 'd, - txdma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sd: Peri<'d, impl MosiPin>, + ws: Peri<'d, impl WsPin>, + ck: Peri<'d, impl CkPin>, + mck: Peri<'d, impl MckPin>, + txdma: Peri<'d, impl TxDma>, + txdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(sd); Self::new_inner( peri, new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), None, ws, ck, - mck, - new_dma!(txdma), + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_dma!(txdma).map(|d| (d, txdma_buf)), None, freq, config, @@ -193,120 +261,236 @@ impl<'d> I2S<'d> { ) } - /// Create a receiver driver - pub fn new_rxonly( - peri: impl Peripheral

+ 'd, - sd: impl Peripheral

> + 'd, - ws: impl Peripheral

> + 'd, - ck: impl Peripheral

> + 'd, - mck: impl Peripheral

> + 'd, - #[cfg(not(spi_v3))] txdma: impl Peripheral

> + 'd, - rxdma: impl Peripheral

> + 'd, + /// Create a transmitter driver without a master clock pin. + pub fn new_txonly_nomck( + peri: Peri<'d, T>, + sd: Peri<'d, impl MosiPin>, + ws: Peri<'d, impl WsPin>, + ck: Peri<'d, impl CkPin>, + txdma: Peri<'d, impl TxDma>, + txdma_buf: &'d mut [W], + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + ws, + ck, + None, + new_dma!(txdma).map(|d| (d, txdma_buf)), + None, + freq, + config, + Function::Transmit, + ) + } + + /// Create a receiver driver. + pub fn new_rxonly( + peri: Peri<'d, T>, + sd: Peri<'d, impl MisoPin>, + ws: Peri<'d, impl WsPin>, + ck: Peri<'d, impl CkPin>, + mck: Peri<'d, impl MckPin>, + rxdma: Peri<'d, impl RxDma>, + rxdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(sd); Self::new_inner( peri, None, new_pin!(sd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), ws, ck, - mck, - #[cfg(not(spi_v3))] - new_dma!(txdma), - #[cfg(spi_v3)] + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), None, - new_dma!(rxdma), + new_dma!(rxdma).map(|d| (d, rxdma_buf)), freq, config, - #[cfg(not(spi_v3))] - Function::Transmit, - #[cfg(spi_v3)] Function::Receive, ) } #[cfg(spi_v3)] - /// Create a full duplex transmitter driver + /// Create a full duplex driver. pub fn new_full_duplex( - peri: impl Peripheral

+ 'd, - txsd: impl Peripheral

> + 'd, - rxsd: impl Peripheral

> + 'd, - ws: impl Peripheral

> + 'd, - ck: impl Peripheral

> + 'd, - mck: impl Peripheral

> + 'd, - txdma: impl Peripheral

> + 'd, - rxdma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + txsd: Peri<'d, impl MosiPin>, + rxsd: Peri<'d, impl MisoPin>, + ws: Peri<'d, impl WsPin>, + ck: Peri<'d, impl CkPin>, + mck: Peri<'d, impl MckPin>, + txdma: Peri<'d, impl TxDma>, + txdma_buf: &'d mut [W], + rxdma: Peri<'d, impl RxDma>, + rxdma_buf: &'d mut [W], freq: Hertz, config: Config, ) -> Self { - into_ref!(txsd, rxsd); Self::new_inner( peri, new_pin!(txsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), new_pin!(rxsd, AfType::output(OutputType::PushPull, Speed::VeryHigh)), ws, ck, - mck, - new_dma!(txdma), - new_dma!(rxdma), + new_pin!(mck, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_dma!(txdma).map(|d| (d, txdma_buf)), + new_dma!(rxdma).map(|d| (d, rxdma_buf)), freq, config, Function::FullDuplex, ) } - /// Write audio data. - pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { - self._peri.read(data).await + /// Start I2S driver. + pub fn start(&mut self) { + self.spi.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + self.spi.set_word_size(W::CONFIG); + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.start(); + + set_txdmaen(self.spi.info.regs, true); + } + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.start(); + // SPIv3 clears rxfifo on SPE=0 + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + flush_rx_fifo(self.spi.info.regs); + + set_rxdmaen(self.spi.info.regs, true); + } + self.spi.info.regs.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + self.spi.info.regs.cr1().modify(|w| { + w.set_cstart(true); + }); } - /// Write audio data. - pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { - self._peri.write(data).await + /// Reset the ring buffer to its initial state. + /// Can be used to recover from overrun. + pub fn clear(&mut self) { + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.clear(); + } + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.clear(); + } } - /// Transfer audio data. - pub async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { - self._peri.transfer(read, write).await + /// Stop I2S driver. + pub async fn stop(&mut self) { + let regs = self.spi.info.regs; + + let tx_f = async { + if let Some(tx_ring_buffer) = &mut self.tx_ring_buffer { + tx_ring_buffer.stop().await; + + set_txdmaen(regs, false); + } + }; + + let rx_f = async { + if let Some(rx_ring_buffer) = &mut self.rx_ring_buffer { + rx_ring_buffer.stop().await; + + set_rxdmaen(regs, false); + } + }; + + join(rx_f, tx_f).await; + + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { + if let Mode::Master = self.mode { + regs.cr1().modify(|w| { + w.set_csusp(true); + }); + + while regs.cr1().read().cstart() {} + } + } + + regs.cr1().modify(|w| { + w.set_spe(false); + }); + + self.clear(); } - /// Transfer audio data in place. - pub async fn transfer_in_place(&mut self, data: &mut [W]) -> Result<(), Error> { - self._peri.transfer_in_place(data).await + /// Split the driver into a Reader/Writer pair. + /// Useful for splitting the reader/writer functionality across tasks or + /// for calling the read/write methods in parallel. + pub fn split<'s>(&'s mut self) -> Result<(Reader<'s, 'd, W>, Writer<'s, 'd, W>), Error> { + match (&mut self.rx_ring_buffer, &mut self.tx_ring_buffer) { + (None, _) => Err(Error::NotAReceiver), + (_, None) => Err(Error::NotATransmitter), + (Some(rx_ring), Some(tx_ring)) => Ok((Reader(rx_ring), Writer(tx_ring))), + } + } + + /// Read data from the I2S ringbuffer. + /// SAI is always receiving data in the background. This function pops already-received data from the buffer. + /// If there’s less than data.len() data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> { + match &mut self.rx_ring_buffer { + Some(ring) => Reader(ring).read(data).await, + _ => Err(Error::NotAReceiver), + } + } + + /// Write data to the I2S ringbuffer. + /// This appends the data to the buffer and returns immediately. The data will be transmitted in the background. + /// If thfre’s no space in the buffer, this waits until there is. + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { + match &mut self.tx_ring_buffer { + Some(ring) => Writer(ring).write(data).await, + _ => Err(Error::NotATransmitter), + } + } + + /// Write data directly to the raw I2S ringbuffer. + /// This can be used to fill the buffer before starting the DMA transfer. + pub async fn write_immediate(&mut self, data: &[W]) -> Result<(usize, usize), Error> { + match &mut self.tx_ring_buffer { + Some(ring) => Ok(ring.write_immediate(data)?), + _ => return Err(Error::NotATransmitter), + } } fn new_inner( - peri: impl Peripheral

+ 'd, - txsd: Option>, - rxsd: Option>, - ws: impl Peripheral

> + 'd, - ck: impl Peripheral

> + 'd, - mck: impl Peripheral

> + 'd, - txdma: Option>, - rxdma: Option>, + peri: Peri<'d, T>, + txsd: Option>, + rxsd: Option>, + ws: Peri<'d, impl WsPin>, + ck: Peri<'d, impl CkPin>, + mck: Option>, + txdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, + rxdma: Option<(ChannelAndRequest<'d>, &'d mut [W])>, freq: Hertz, config: Config, function: Function, ) -> Self { - into_ref!(ws, ck, mck); - ws.set_as_af(ws.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); ck.set_as_af(ck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - mck.set_as_af(mck.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); - let mut spi_cfg = SpiConfig::default(); - spi_cfg.frequency = freq; - - let spi = Spi::new_internal(peri, txdma, rxdma, spi_cfg); + let spi = Spi::new_internal(peri, None, None, { + let mut config = SpiConfig::default(); + config.frequency = freq; + config + }); let regs = T::info().regs; - // TODO move i2s to the new mux infra. - //#[cfg(all(rcc_f4, not(stm32f410)))] - //let pclk = unsafe { get_freqs() }.plli2s1_q.unwrap(); - //#[cfg(stm32f410)] + #[cfg(all(rcc_f4, not(stm32f410)))] + let pclk = unsafe { crate::rcc::get_freqs() }.plli2s1_r.to_hertz().unwrap(); + #[cfg(not(all(rcc_f4, not(stm32f410))))] let pclk = T::frequency(); let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format); @@ -376,36 +560,43 @@ impl<'d> I2S<'d> { w.set_chlen(config.format.chlen()); w.set_i2scfg(match (config.mode, function) { - (Mode::Master, Function::Transmit) => I2scfg::MASTERTX, - (Mode::Master, Function::Receive) => I2scfg::MASTERRX, + (Mode::Master, Function::Transmit) => I2scfg::MASTER_TX, + (Mode::Master, Function::Receive) => I2scfg::MASTER_RX, #[cfg(spi_v3)] - (Mode::Master, Function::FullDuplex) => I2scfg::MASTERFULLDUPLEX, - (Mode::Slave, Function::Transmit) => I2scfg::SLAVETX, - (Mode::Slave, Function::Receive) => I2scfg::SLAVERX, + (Mode::Master, Function::FullDuplex) => I2scfg::MASTER_FULL_DUPLEX, + (Mode::Slave, Function::Transmit) => I2scfg::SLAVE_TX, + (Mode::Slave, Function::Receive) => I2scfg::SLAVE_RX, #[cfg(spi_v3)] - (Mode::Slave, Function::FullDuplex) => I2scfg::SLAVEFULLDUPLEX, + (Mode::Slave, Function::FullDuplex) => I2scfg::SLAVE_FULL_DUPLEX, }); #[cfg(any(spi_v1, spi_f1))] w.set_i2se(true); }); - #[cfg(spi_v3)] - regs.cr1().modify(|w| w.set_spe(true)); - } + let mut opts = TransferOptions::default(); + opts.half_transfer_ir = true; - Self { - _peri: spi, - txsd: txsd.map(|w| w.map_into()), - rxsd: rxsd.map(|w| w.map_into()), - ws: Some(ws.map_into()), - ck: Some(ck.map_into()), - mck: Some(mck.map_into()), + Self { + mode: config.mode, + spi, + txsd: txsd.map(|w| w.into()), + rxsd: rxsd.map(|w| w.into()), + ws: Some(ws.into()), + ck: Some(ck.into()), + mck: mck.map(|w| w.into()), + tx_ring_buffer: txdma.map(|(ch, buf)| unsafe { + WritableRingBuffer::new(ch.channel, ch.request, regs.tx_ptr(), buf, opts) + }), + rx_ring_buffer: rxdma.map(|(ch, buf)| unsafe { + ReadableRingBuffer::new(ch.channel, ch.request, regs.rx_ptr(), buf, opts) + }), + } } } } -impl<'d> Drop for I2S<'d> { +impl<'d, W: Word> Drop for I2S<'d, W> { fn drop(&mut self) { self.txsd.as_ref().map(|x| x.set_as_disconnected()); self.rxsd.as_ref().map(|x| x.set_as_disconnected()); diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs index 6c8347311..20cd20dca 100644 --- a/embassy-stm32/src/ipcc.rs +++ b/embassy-stm32/src/ipcc.rs @@ -229,11 +229,9 @@ struct State { impl State { const fn new() -> Self { - const WAKER: AtomicWaker = AtomicWaker::new(); - Self { - rx_wakers: [WAKER; 6], - tx_wakers: [WAKER; 6], + rx_wakers: [const { AtomicWaker::new() }; 6], + tx_wakers: [const { AtomicWaker::new() }; 6], } } diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 12ebbae2d..973acc9bb 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -68,6 +68,8 @@ pub mod dac; pub mod dcmi; #[cfg(dsihost)] pub mod dsihost; +#[cfg(dts)] +pub mod dts; #[cfg(eth)] pub mod eth; #[cfg(feature = "exti")] @@ -81,6 +83,8 @@ pub mod hash; pub mod hrtim; #[cfg(hsem)] pub mod hsem; +#[cfg(hspi)] +pub mod hspi; #[cfg(i2c)] pub mod i2c; #[cfg(any(all(spi_v1, rcc_f4), spi_v3))] @@ -89,6 +93,8 @@ pub mod i2s; pub mod ipcc; #[cfg(feature = "low-power")] pub mod low_power; +#[cfg(lptim)] +pub mod lptim; #[cfg(ltdc)] pub mod ltdc; #[cfg(opamp)] @@ -105,6 +111,8 @@ pub mod rtc; pub mod sai; #[cfg(sdmmc)] pub mod sdmmc; +#[cfg(spdifrx)] +pub mod spdifrx; #[cfg(spi)] pub mod spi; #[cfg(tsc)] @@ -119,6 +127,8 @@ pub mod usart; pub mod usb; #[cfg(iwdg)] pub mod wdg; +#[cfg(xspi)] +pub mod xspi; // This must go last, so that it sees all the impl_foo! macros defined earlier. pub(crate) mod _generated { @@ -153,39 +163,63 @@ pub use crate::_generated::interrupt; /// ```rust,ignore /// use embassy_stm32::{bind_interrupts, i2c, peripherals}; /// -/// bind_interrupts!(struct Irqs { -/// I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; -/// I2C2_3 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler, -/// i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; -/// }); +/// bind_interrupts!( +/// /// Binds the I2C interrupts. +/// struct Irqs { +/// I2C1 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +/// I2C2_3 => i2c::EventInterruptHandler, i2c::ErrorInterruptHandler, +/// i2c::EventInterruptHandler, i2c::ErrorInterruptHandler; +/// } +/// ); /// ``` // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + ($(#[$outer:meta])* $vis:vis struct $name:ident { + $( + $(#[$inner:meta])* + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { #[derive(Copy, Clone)] + $(#[$outer])* $vis struct $name; $( #[allow(non_snake_case)] #[no_mangle] + $(#[cfg($cond_irq)])? + $(#[$inner])* unsafe extern "C" fn $irq() { $( + $(#[cfg($cond_handler)])? <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* } - $( - unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} - )* + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); )* }; + (@inner $($t:tt)*) => { + $($t)* + } } // Reexports pub use _generated::{peripherals, Peripherals}; -pub use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use embassy_hal_internal::{Peri, PeripheralType}; #[cfg(feature = "unstable-pac")] pub use stm32_metapac as pac; #[cfg(not(feature = "unstable-pac"))] @@ -197,6 +231,7 @@ pub use crate::pac::NVIC_PRIO_BITS; /// `embassy-stm32` global configuration. #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { /// RCC config. pub rcc: rcc::Config, @@ -215,6 +250,10 @@ pub struct Config { #[cfg(any(stm32l4, stm32l5, stm32u5))] pub enable_independent_io_supply: bool, + /// On the U5 series all analog peripherals are powered by a separate supply. + #[cfg(stm32u5)] + pub enable_independent_analog_supply: bool, + /// BDMA interrupt priority. /// /// Defaults to P0 (highest). @@ -254,6 +293,8 @@ impl Default for Config { enable_debug_during_sleep: true, #[cfg(any(stm32l4, stm32l5, stm32u5))] enable_independent_io_supply: true, + #[cfg(stm32u5)] + enable_independent_analog_supply: true, #[cfg(bdma)] bdma_interrupt_priority: Priority::P0, #[cfg(dma)] @@ -293,6 +334,9 @@ mod dual_core { /// It cannot be initialized by the user. The intended use is: /// /// ``` + /// use core::mem::MaybeUninit; + /// use embassy_stm32::{init_secondary, SharedData}; + /// /// #[link_section = ".ram_d3"] /// static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); /// @@ -300,9 +344,11 @@ mod dual_core { /// ``` /// /// This static must be placed in the same position for both cores. How and where this is done is left to the user. + #[repr(C)] pub struct SharedData { init_flag: AtomicUsize, clocks: UnsafeCell>, + config: UnsafeCell>, } unsafe impl Sync for SharedData {} @@ -322,9 +368,16 @@ mod dual_core { pub fn init_primary(config: Config, shared_data: &'static MaybeUninit) -> Peripherals { let shared_data = unsafe { shared_data.assume_init_ref() }; + // Write the flag as soon as possible. Reading this flag uninitialized in the `init_secondary` + // is maybe unsound? Unclear. If it is indeed unsound, writing it sooner doesn't fix it all, + // but improves the odds of it going right + shared_data.init_flag.store(0, Ordering::SeqCst); + rcc::set_freqs_ptr(shared_data.clocks.get()); let p = init_hw(config); + unsafe { *shared_data.config.get() }.write(config.into()); + shared_data.init_flag.store(INIT_DONE_FLAG, Ordering::SeqCst); p @@ -372,15 +425,63 @@ mod dual_core { fn init_secondary_hw(shared_data: &'static SharedData) -> Peripherals { rcc::set_freqs_ptr(shared_data.clocks.get()); + let config = unsafe { (*shared_data.config.get()).assume_init() }; + // We use different timers on the different cores, so we have to still initialize one here - #[cfg(feature = "_time-driver")] critical_section::with(|cs| { + unsafe { + dma::init( + cs, + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, + ) + } + + #[cfg(feature = "_time-driver")] // must be after rcc init time_driver::init(cs); }); Peripherals::take() } + + #[repr(C)] + #[derive(Clone, Copy)] + struct SharedConfig { + #[cfg(bdma)] + bdma_interrupt_priority: Priority, + #[cfg(dma)] + dma_interrupt_priority: Priority, + #[cfg(gpdma)] + gpdma_interrupt_priority: Priority, + } + + impl From for SharedConfig { + fn from(value: Config) -> Self { + let Config { + #[cfg(bdma)] + bdma_interrupt_priority, + #[cfg(dma)] + dma_interrupt_priority, + #[cfg(gpdma)] + gpdma_interrupt_priority, + .. + } = value; + + SharedConfig { + #[cfg(bdma)] + bdma_interrupt_priority, + #[cfg(dma)] + dma_interrupt_priority, + #[cfg(gpdma)] + gpdma_interrupt_priority, + } + } + } } #[cfg(feature = "_dual-core")] @@ -397,7 +498,7 @@ fn init_hw(config: Config) -> Peripherals { 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))] + #[cfg(any(dbgmcu_f0, dbgmcu_c0, dbgmcu_g0, dbgmcu_u0, dbgmcu_u5, dbgmcu_wba, dbgmcu_l5))] { cr.set_dbg_stop(config.enable_debug_during_sleep); cr.set_dbg_standby(config.enable_debug_during_sleep); @@ -444,6 +545,20 @@ fn init_hw(config: Config) -> Peripherals { crate::pac::PWR.svmcr().modify(|w| { w.set_io2sv(config.enable_independent_io_supply); }); + if config.enable_independent_analog_supply { + crate::pac::PWR.svmcr().modify(|w| { + w.set_avm1en(true); + }); + while !crate::pac::PWR.svmsr().read().vdda1rdy() {} + crate::pac::PWR.svmcr().modify(|w| { + w.set_asv(true); + }); + } else { + crate::pac::PWR.svmcr().modify(|w| { + w.set_avm1en(false); + w.set_avm2en(false); + }); + } } // dead battery functionality is still present on these @@ -491,17 +606,7 @@ fn init_hw(config: Config) -> Peripherals { #[cfg(feature = "exti")] exti::init(cs); - rcc::init(config.rcc); - - // must be after rcc init - #[cfg(feature = "_time-driver")] - time_driver::init(cs); - - #[cfg(feature = "low-power")] - { - crate::rcc::REFCOUNT_STOP2 = 0; - crate::rcc::REFCOUNT_STOP1 = 0; - } + rcc::init_rcc(cs, config.rcc); } p diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 604bdf416..7734365f1 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -51,6 +51,9 @@ //! } //! ``` +// TODO: Usage of `static mut` here is unsound. Fix then remove this `allow`.` +#![allow(static_mut_refs)] + use core::arch::asm; use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; @@ -67,6 +70,7 @@ use crate::rtc::Rtc; static mut EXECUTOR: Option = None; +#[cfg(not(stm32u0))] foreach_interrupt! { (RTC, rtc, $block:ident, WKUP, $irq:ident) => { #[interrupt] @@ -77,6 +81,17 @@ foreach_interrupt! { }; } +#[cfg(stm32u0)] +foreach_interrupt! { + (RTC, rtc, $block:ident, TAMP, $irq:ident) => { + #[interrupt] + #[allow(non_snake_case)] + unsafe fn $irq() { + EXECUTOR.as_mut().unwrap().on_wakeup_irq(); + } + }; +} + #[allow(dead_code)] pub(crate) unsafe fn on_wakeup_irq() { EXECUTOR.as_mut().unwrap().on_wakeup_irq(); @@ -109,10 +124,10 @@ pub enum StopMode { Stop2, } -#[cfg(stm32l5)] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0))] use stm32_metapac::pwr::vals::Lpms; -#[cfg(stm32l5)] +#[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0))] impl Into for StopMode { fn into(self) -> Lpms { match self { @@ -152,7 +167,9 @@ impl Executor { time_driver: get_driver(), }); - EXECUTOR.as_mut().unwrap() + let executor = EXECUTOR.as_mut().unwrap(); + + executor }) } @@ -181,7 +198,7 @@ impl Executor { #[allow(unused_variables)] fn configure_stop(&mut self, stop_mode: StopMode) { - #[cfg(stm32l5)] + #[cfg(any(stm32l4, stm32l5, stm32u5, stm32u0))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); #[cfg(stm32h5)] crate::pac::PWR.pmcr().modify(|v| { @@ -238,11 +255,12 @@ impl Executor { /// /// This function never returns. pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(unsafe { EXECUTOR.as_mut().unwrap() }.inner.spawner()); + let executor = unsafe { EXECUTOR.as_mut().unwrap() }; + init(executor.inner.spawner()); loop { unsafe { - EXECUTOR.as_mut().unwrap().inner.poll(); + executor.inner.poll(); self.configure_pwr(); asm!("wfe"); }; diff --git a/embassy-stm32/src/lptim/channel.rs b/embassy-stm32/src/lptim/channel.rs new file mode 100644 index 000000000..17fc2fb86 --- /dev/null +++ b/embassy-stm32/src/lptim/channel.rs @@ -0,0 +1,18 @@ +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { + /// Channel 1. + Ch1, + /// Channel 2. + Ch2, +} + +impl Channel { + /// Get the channel index (0..1) + pub fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, + } + } +} diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs new file mode 100644 index 000000000..e0ddba1c7 --- /dev/null +++ b/embassy-stm32/src/lptim/mod.rs @@ -0,0 +1,49 @@ +//! Low-power timer (LPTIM) + +pub mod pwm; +pub mod timer; + +use crate::rcc::RccPeripheral; + +/// Timer channel. +#[cfg(any(lptim_v2a, lptim_v2b))] +mod channel; +#[cfg(any(lptim_v2a, lptim_v2b))] +pub use channel::Channel; +use embassy_hal_internal::PeripheralType; + +pin_trait!(OutputPin, BasicInstance); +pin_trait!(Channel1Pin, BasicInstance); +pin_trait!(Channel2Pin, BasicInstance); + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::lptim::Lptim; +} +pub(crate) trait SealedBasicInstance: RccPeripheral {} + +/// LPTIM basic instance trait. +#[allow(private_bounds)] +pub trait BasicInstance: PeripheralType + SealedBasicInstance + 'static {} + +/// LPTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: BasicInstance + SealedInstance + 'static {} + +foreach_interrupt! { + ($inst:ident, lptim, LPTIM, GLOBAL, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::lptim::Lptim { + crate::pac::$inst + } + } + impl SealedBasicInstance for crate::peripherals::$inst { + } + impl BasicInstance for crate::peripherals::$inst {} + impl Instance for crate::peripherals::$inst {} + }; + ($inst:ident, lptim, LPTIM_BASIC, GLOBAL, $irq:ident) => { + impl SealedBasicInstance for crate::peripherals::$inst { + } + impl BasicInstance for crate::peripherals::$inst {} + }; +} diff --git a/embassy-stm32/src/lptim/pwm.rs b/embassy-stm32/src/lptim/pwm.rs new file mode 100644 index 000000000..2f2d7ba01 --- /dev/null +++ b/embassy-stm32/src/lptim/pwm.rs @@ -0,0 +1,198 @@ +//! PWM driver. + +use core::marker::PhantomData; + +use embassy_hal_internal::Peri; + +use super::timer::Timer; +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +use super::OutputPin; +#[cfg(any(lptim_v2a, lptim_v2b))] +use super::{channel::Channel, timer::ChannelDirection, Channel1Pin, Channel2Pin}; +use super::{BasicInstance, Instance}; +#[cfg(gpio_v2)] +use crate::gpio::Pull; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::time::Hertz; + +/// Output marker type. +pub enum Output {} +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} + +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct PwmPin<'d, T, C> { + _pin: Peri<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +/// PWM pin config +/// +/// This configures the pwm pin settings +pub struct PwmPinConfig { + /// PWM Pin output type + pub output_type: OutputType, + /// PWM Pin speed + pub speed: Speed, + /// PWM Pin pull type + #[cfg(gpio_v2)] + pub pull: Pull, +} + +macro_rules! channel_impl { + ($new_chx:ident, $new_chx_with_config:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: BasicInstance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>) -> Self { + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + AfType::output(OutputType::PushPull, Speed::VeryHigh), + ); + }); + PwmPin { + _pin: pin.into(), + phantom: PhantomData, + } + } + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance with config.")] + pub fn $new_chx_with_config(pin: Peri<'d, impl $pin_trait>, pin_config: PwmPinConfig) -> Self { + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + #[cfg(gpio_v1)] + AfType::output(pin_config.output_type, pin_config.speed), + #[cfg(gpio_v2)] + AfType::output_pull(pin_config.output_type, pin_config.speed, pin_config.pull), + ); + }); + PwmPin { + _pin: pin.into(), + phantom: PhantomData, + } + } + } + }; +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +channel_impl!(new, new_with_config, Output, OutputPin); +#[cfg(any(lptim_v2a, lptim_v2b))] +channel_impl!(new_ch1, new_ch1_with_config, Ch1, Channel1Pin); +#[cfg(any(lptim_v2a, lptim_v2b))] +channel_impl!(new_ch2, new_ch2_with_config, Ch2, Channel2Pin); + +/// PWM driver. +pub struct Pwm<'d, T: Instance> { + inner: Timer<'d, T>, +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new PWM driver. + pub fn new(tim: Peri<'d, T>, _output_pin: PwmPin<'d, T, Output>, freq: Hertz) -> Self { + Self::new_inner(tim, freq) + } + + /// Set the duty. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, duty: u16) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(duty) + } + + /// Get the duty. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn get_duty(&self) -> u16 { + self.inner.get_compare_value() + } + + fn post_init(&mut self) {} +} + +#[cfg(any(lptim_v2a, lptim_v2b))] +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new PWM driver. + pub fn new( + tim: Peri<'d, T>, + _ch1_pin: Option>, + _ch2_pin: Option>, + freq: Hertz, + ) -> Self { + Self::new_inner(tim, freq) + } + + /// Enable the given channel. + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self, channel: Channel) -> bool { + self.inner.get_channel_enable_state(channel) + } + + /// Set the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, channel: Channel, duty: u16) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(channel, duty) + } + + /// Get the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn get_duty(&self, channel: Channel) -> u16 { + self.inner.get_compare_value(channel) + } + + fn post_init(&mut self) { + [Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| { + self.inner.set_channel_direction(channel, ChannelDirection::OutputPwm); + }); + } +} + +impl<'d, T: Instance> Pwm<'d, T> { + fn new_inner(tim: Peri<'d, T>, freq: Hertz) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.enable(); + this.set_frequency(freq); + + this.post_init(); + + this.inner.continuous_mode_start(); + + this + } + + /// Set PWM frequency. + /// + /// Note: when you call this, the max duty value changes, so you will have to + /// call `set_duty` on all channels with the duty calculated based on the new max duty. + pub fn set_frequency(&mut self, frequency: Hertz) { + self.inner.set_frequency(frequency); + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn get_max_duty(&self) -> u16 { + self.inner.get_max_compare_value() + 1 + } +} diff --git a/embassy-stm32/src/lptim/timer/channel_direction.rs b/embassy-stm32/src/lptim/timer/channel_direction.rs new file mode 100644 index 000000000..e4af8f45f --- /dev/null +++ b/embassy-stm32/src/lptim/timer/channel_direction.rs @@ -0,0 +1,18 @@ +use crate::pac::lptim::vals; + +/// Direction of a low-power timer channel +pub enum ChannelDirection { + /// Use channel as a PWM output + OutputPwm, + /// Use channel as an input capture + InputCapture, +} + +impl From for vals::Ccsel { + fn from(direction: ChannelDirection) -> Self { + match direction { + ChannelDirection::OutputPwm => vals::Ccsel::OUTPUT_COMPARE, + ChannelDirection::InputCapture => vals::Ccsel::INPUT_CAPTURE, + } + } +} diff --git a/embassy-stm32/src/lptim/timer/mod.rs b/embassy-stm32/src/lptim/timer/mod.rs new file mode 100644 index 000000000..a629be62b --- /dev/null +++ b/embassy-stm32/src/lptim/timer/mod.rs @@ -0,0 +1,131 @@ +//! Low-level timer driver. +mod prescaler; + +use embassy_hal_internal::Peri; + +#[cfg(any(lptim_v2a, lptim_v2b))] +use super::channel::Channel; +#[cfg(any(lptim_v2a, lptim_v2b))] +mod channel_direction; +#[cfg(any(lptim_v2a, lptim_v2b))] +pub use channel_direction::ChannelDirection; +use prescaler::Prescaler; + +use super::Instance; +use crate::rcc; +use crate::time::Hertz; + +/// Low-level timer driver. +pub struct Timer<'d, T: Instance> { + _tim: Peri<'d, T>, +} + +impl<'d, T: Instance> Timer<'d, T> { + /// Create a new timer driver. + pub fn new(tim: Peri<'d, T>) -> Self { + rcc::enable_and_reset::(); + + Self { _tim: tim } + } + + /// Enable the timer. + pub fn enable(&self) { + T::regs().cr().modify(|w| w.set_enable(true)); + } + + /// Disable the timer. + pub fn disable(&self) { + T::regs().cr().modify(|w| w.set_enable(false)); + } + + /// Start the timer in single pulse mode. + pub fn single_mode_start(&self) { + T::regs().cr().modify(|w| w.set_sngstrt(true)); + } + + /// Start the timer in continuous mode. + pub fn continuous_mode_start(&self) { + T::regs().cr().modify(|w| w.set_cntstrt(true)); + } + + /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. + pub fn set_frequency(&self, frequency: Hertz) { + let f = frequency.0; + assert!(f > 0); + + let pclk_f = T::frequency().0; + + let pclk_ticks_per_timer_period = pclk_f / f; + + let psc = Prescaler::from_ticks(pclk_ticks_per_timer_period); + let arr = psc.scale_down(pclk_ticks_per_timer_period); + + T::regs().cfgr().modify(|r| r.set_presc((&psc).into())); + T::regs().arr().modify(|r| r.set_arr(arr.into())); + } + + /// Get the timer frequency. + pub fn get_frequency(&self) -> Hertz { + let pclk_f = T::frequency(); + let arr = T::regs().arr().read().arr(); + let psc = Prescaler::from(T::regs().cfgr().read().presc()); + + pclk_f / psc.scale_up(arr) + } + + /// Get the clock frequency of the timer (before prescaler is applied). + pub fn get_clock_frequency(&self) -> Hertz { + T::frequency() + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + pub fn get_max_compare_value(&self) -> u16 { + T::regs().arr().read().arr() + } +} + +#[cfg(any(lptim_v2a, lptim_v2b))] +impl<'d, T: Instance> Timer<'d, T> { + /// Enable/disable a channel. + pub fn enable_channel(&self, channel: Channel, enable: bool) { + T::regs().ccmr(0).modify(|w| { + w.set_cce(channel.index(), enable); + }); + } + + /// Get enable/disable state of a channel + pub fn get_channel_enable_state(&self, channel: Channel) -> bool { + T::regs().ccmr(0).read().cce(channel.index()) + } + + /// Set compare value for a channel. + pub fn set_compare_value(&self, channel: Channel, value: u16) { + T::regs().ccr(channel.index()).modify(|w| w.set_ccr(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self, channel: Channel) -> u16 { + T::regs().ccr(channel.index()).read().ccr() + } + + /// Set channel direction. + #[cfg(any(lptim_v2a, lptim_v2b))] + pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { + T::regs() + .ccmr(0) + .modify(|w| w.set_ccsel(channel.index(), direction.into())); + } +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +impl<'d, T: Instance> Timer<'d, T> { + /// Set compare value for a channel. + pub fn set_compare_value(&self, value: u16) { + T::regs().cmp().modify(|w| w.set_cmp(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self) -> u16 { + T::regs().cmp().read().cmp() + } +} diff --git a/embassy-stm32/src/lptim/timer/prescaler.rs b/embassy-stm32/src/lptim/timer/prescaler.rs new file mode 100644 index 000000000..5d2326faf --- /dev/null +++ b/embassy-stm32/src/lptim/timer/prescaler.rs @@ -0,0 +1,90 @@ +//! Low-level timer driver. + +use crate::pac::lptim::vals; + +pub enum Prescaler { + Div1, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl From<&Prescaler> for vals::Presc { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => vals::Presc::DIV1, + Prescaler::Div2 => vals::Presc::DIV2, + Prescaler::Div4 => vals::Presc::DIV4, + Prescaler::Div8 => vals::Presc::DIV8, + Prescaler::Div16 => vals::Presc::DIV16, + Prescaler::Div32 => vals::Presc::DIV32, + Prescaler::Div64 => vals::Presc::DIV64, + Prescaler::Div128 => vals::Presc::DIV128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: vals::Presc) -> Self { + match prescaler { + vals::Presc::DIV1 => Prescaler::Div1, + vals::Presc::DIV2 => Prescaler::Div2, + vals::Presc::DIV4 => Prescaler::Div4, + vals::Presc::DIV8 => Prescaler::Div8, + vals::Presc::DIV16 => Prescaler::Div16, + vals::Presc::DIV32 => Prescaler::Div32, + vals::Presc::DIV64 => Prescaler::Div64, + vals::Presc::DIV128 => Prescaler::Div128, + } + } +} + +impl From<&Prescaler> for u32 { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => 1, + Prescaler::Div2 => 2, + Prescaler::Div4 => 4, + Prescaler::Div8 => 8, + Prescaler::Div16 => 16, + Prescaler::Div32 => 32, + Prescaler::Div64 => 64, + Prescaler::Div128 => 128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: u32) -> Self { + match prescaler { + 1 => Prescaler::Div1, + 2 => Prescaler::Div2, + 4 => Prescaler::Div4, + 8 => Prescaler::Div8, + 16 => Prescaler::Div16, + 32 => Prescaler::Div32, + 64 => Prescaler::Div64, + 128 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn from_ticks(ticks: u32) -> Self { + // We need to scale down to a 16-bit range + (ticks >> 16).next_power_of_two().into() + } + + pub fn scale_down(&self, ticks: u32) -> u16 { + (ticks / u32::from(self)).try_into().unwrap() + } + + pub fn scale_up(&self, ticks: u16) -> u32 { + u32::from(self) * ticks as u32 + } +} diff --git a/embassy-stm32/src/ltdc.rs b/embassy-stm32/src/ltdc.rs index 4c5239971..0f6ef569c 100644 --- a/embassy-stm32/src/ltdc.rs +++ b/embassy-stm32/src/ltdc.rs @@ -6,7 +6,7 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use stm32_metapac::ltdc::regs::Dccr; use stm32_metapac::ltdc::vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr}; @@ -14,7 +14,7 @@ use stm32_metapac::ltdc::vals::{Bf1, Bf2, Cfuif, Clif, Crrif, Cterrif, Pf, Vbr}; use crate::gpio::{AfType, OutputType, Speed}; use crate::interrupt::typelevel::Interrupt; use crate::interrupt::{self}; -use crate::{peripherals, rcc, Peripheral}; +use crate::{peripherals, rcc, Peri}; static LTDC_WAKER: AtomicWaker = AtomicWaker::new(); @@ -83,7 +83,7 @@ pub enum PolarityActive { /// LTDC driver. pub struct Ltdc<'d, T: Instance> { - _peri: PeripheralRef<'d, T>, + _peri: Peri<'d, T>, } /// LTDC interrupt handler. @@ -178,47 +178,45 @@ impl interrupt::typelevel::Handler for InterruptHandl impl<'d, T: Instance> Ltdc<'d, T> { // Create a new LTDC driver without specifying color and control pins. This is typically used if you want to drive a display though a DsiHost /// Note: Full-Duplex modes are not supported at this time - pub fn new(peri: impl Peripheral

+ 'd) -> Self { + pub fn new(peri: Peri<'d, T>) -> Self { Self::setup_clocks(); - into_ref!(peri); Self { _peri: peri } } /// Create a new LTDC driver. 8 pins per color channel for blue, green and red #[allow(clippy::too_many_arguments)] pub fn new_with_pins( - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - clk: impl Peripheral

> + 'd, - hsync: impl Peripheral

> + 'd, - vsync: impl Peripheral

> + 'd, - b0: impl Peripheral

> + 'd, - b1: impl Peripheral

> + 'd, - b2: impl Peripheral

> + 'd, - b3: impl Peripheral

> + 'd, - b4: impl Peripheral

> + 'd, - b5: impl Peripheral

> + 'd, - b6: impl Peripheral

> + 'd, - b7: impl Peripheral

> + 'd, - g0: impl Peripheral

> + 'd, - g1: impl Peripheral

> + 'd, - g2: impl Peripheral

> + 'd, - g3: impl Peripheral

> + 'd, - g4: impl Peripheral

> + 'd, - g5: impl Peripheral

> + 'd, - g6: impl Peripheral

> + 'd, - g7: impl Peripheral

> + 'd, - r0: impl Peripheral

> + 'd, - r1: impl Peripheral

> + 'd, - r2: impl Peripheral

> + 'd, - r3: impl Peripheral

> + 'd, - r4: impl Peripheral

> + 'd, - r5: impl Peripheral

> + 'd, - r6: impl Peripheral

> + 'd, - r7: impl Peripheral

> + 'd, + clk: Peri<'d, impl ClkPin>, + hsync: Peri<'d, impl HsyncPin>, + vsync: Peri<'d, impl VsyncPin>, + b0: Peri<'d, impl B0Pin>, + b1: Peri<'d, impl B1Pin>, + b2: Peri<'d, impl B2Pin>, + b3: Peri<'d, impl B3Pin>, + b4: Peri<'d, impl B4Pin>, + b5: Peri<'d, impl B5Pin>, + b6: Peri<'d, impl B6Pin>, + b7: Peri<'d, impl B7Pin>, + g0: Peri<'d, impl G0Pin>, + g1: Peri<'d, impl G1Pin>, + g2: Peri<'d, impl G2Pin>, + g3: Peri<'d, impl G3Pin>, + g4: Peri<'d, impl G4Pin>, + g5: Peri<'d, impl G5Pin>, + g6: Peri<'d, impl G6Pin>, + g7: Peri<'d, impl G7Pin>, + r0: Peri<'d, impl R0Pin>, + r1: Peri<'d, impl R1Pin>, + r2: Peri<'d, impl R2Pin>, + r3: Peri<'d, impl R3Pin>, + r4: Peri<'d, impl R4Pin>, + r5: Peri<'d, impl R5Pin>, + r6: Peri<'d, impl R6Pin>, + r7: Peri<'d, impl R7Pin>, ) -> Self { Self::setup_clocks(); - into_ref!(peri); new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)); new_pin!(hsync, AfType::output(OutputType::PushPull, Speed::VeryHigh)); new_pin!(vsync, AfType::output(OutputType::PushPull, Speed::VeryHigh)); @@ -261,23 +259,23 @@ impl<'d, T: Instance> Ltdc<'d, T> { // configure the HS, VS, DE and PC polarity ltdc.gcr().modify(|w| { w.set_hspol(match config.h_sync_polarity { - PolarityActive::ActiveHigh => Hspol::ACTIVEHIGH, - PolarityActive::ActiveLow => Hspol::ACTIVELOW, + PolarityActive::ActiveHigh => Hspol::ACTIVE_HIGH, + PolarityActive::ActiveLow => Hspol::ACTIVE_LOW, }); w.set_vspol(match config.v_sync_polarity { - PolarityActive::ActiveHigh => Vspol::ACTIVEHIGH, - PolarityActive::ActiveLow => Vspol::ACTIVELOW, + PolarityActive::ActiveHigh => Vspol::ACTIVE_HIGH, + PolarityActive::ActiveLow => Vspol::ACTIVE_LOW, }); w.set_depol(match config.data_enable_polarity { - PolarityActive::ActiveHigh => Depol::ACTIVEHIGH, - PolarityActive::ActiveLow => Depol::ACTIVELOW, + PolarityActive::ActiveHigh => Depol::ACTIVE_HIGH, + PolarityActive::ActiveLow => Depol::ACTIVE_LOW, }); w.set_pcpol(match config.pixel_clock_polarity { - PolarityEdge::RisingEdge => Pcpol::RISINGEDGE, - PolarityEdge::FallingEdge => Pcpol::FALLINGEDGE, + PolarityEdge::RisingEdge => Pcpol::RISING_EDGE, + PolarityEdge::FallingEdge => Pcpol::FALLING_EDGE, }); }); @@ -395,7 +393,10 @@ impl<'d, T: Instance> Ltdc<'d, T> { // framebuffer pitch and line length layer.cfblr().modify(|w| { w.set_cfbp(width * bytes_per_pixel); + #[cfg(not(stm32u5))] w.set_cfbll(width * bytes_per_pixel + 7); + #[cfg(stm32u5)] + w.set_cfbll(width * bytes_per_pixel + 3); }); // framebuffer line number @@ -526,7 +527,7 @@ trait SealedInstance: crate::rcc::SealedRccPeripheral { /// LTDC instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + crate::rcc::RccPeripheral + 'static + Send { /// Interrupt for this LTDC instance. type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-stm32/src/macros.rs b/embassy-stm32/src/macros.rs index ae53deb08..7526bb180 100644 --- a/embassy-stm32/src/macros.rs +++ b/embassy-stm32/src/macros.rs @@ -14,7 +14,7 @@ macro_rules! peri_trait { /// Peripheral instance trait. #[allow(private_bounds)] - pub trait Instance: crate::Peripheral

+ SealedInstance + crate::rcc::RccPeripheral { + pub trait Instance: SealedInstance + crate::PeripheralType + crate::rcc::RccPeripheral { $($( /// Interrupt for this peripheral. type $irq: crate::interrupt::typelevel::Interrupt; @@ -60,6 +60,17 @@ macro_rules! pin_trait_impl { }; } +#[allow(unused_macros)] +macro_rules! sel_trait_impl { + (crate::$mod:ident::$trait:ident$(<$mode:ident>)?, $instance:ident, $pin:ident, $sel:expr) => { + impl crate::$mod::$trait for crate::peripherals::$pin { + fn sel(&self) -> u8 { + $sel + } + } + }; +} + // ==================== macro_rules! dma_trait { @@ -85,12 +96,24 @@ macro_rules! dma_trait_impl { }; } +#[allow(unused)] +macro_rules! new_dma_nonopt { + ($name:ident) => {{ + let dma = $name; + let request = dma.request(); + crate::dma::ChannelAndRequest { + channel: dma.into(), + request, + } + }}; +} + macro_rules! new_dma { ($name:ident) => {{ - let dma = $name.into_ref(); + let dma = $name; let request = dma.request(); Some(crate::dma::ChannelAndRequest { - channel: dma.map_into(), + channel: dma.into(), request, }) }}; @@ -98,8 +121,8 @@ macro_rules! new_dma { macro_rules! new_pin { ($name:ident, $af_type:expr) => {{ - let pin = $name.into_ref(); + let pin = $name; pin.set_as_af(pin.af_num(), $af_type); - Some(pin.map_into()) + Some(pin.into()) }}; } diff --git a/embassy-stm32/src/opamp.rs b/embassy-stm32/src/opamp.rs index c86c18e22..2eb2e61c1 100644 --- a/embassy-stm32/src/opamp.rs +++ b/embassy-stm32/src/opamp.rs @@ -1,40 +1,48 @@ //! Operational Amplifier (OPAMP) #![macro_use] -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; use crate::pac::opamp::vals::*; -use crate::Peripheral; +use crate::Peri; + +/// Performs a busy-wait delay for a specified number of microseconds. +#[cfg(opamp_g4)] +fn blocking_delay_ms(ms: u32) { + #[cfg(feature = "time")] + embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64)); + #[cfg(not(feature = "time"))] + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms); +} /// Gain #[allow(missing_docs)] #[derive(Clone, Copy)] pub enum OpAmpGain { - Mul1, Mul2, Mul4, Mul8, Mul16, + #[cfg(opamp_g4)] + Mul32, + #[cfg(opamp_g4)] + Mul64, +} + +#[cfg(opamp_g4)] +enum OpAmpDifferentialPair { + P, + N, } /// Speed #[allow(missing_docs)] -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub enum OpAmpSpeed { Normal, HighSpeed, } -#[cfg(opamp_g4)] -impl From for crate::pac::opamp::vals::Opahsm { - fn from(v: OpAmpSpeed) -> Self { - match v { - OpAmpSpeed::Normal => crate::pac::opamp::vals::Opahsm::NORMAL, - OpAmpSpeed::HighSpeed => crate::pac::opamp::vals::Opahsm::HIGHSPEED, - } - } -} - /// OpAmp external outputs, wired to a GPIO pad. /// /// This struct can also be used as an ADC input. @@ -52,19 +60,17 @@ pub struct OpAmpInternalOutput<'d, T: Instance> { /// OpAmp driver. pub struct OpAmp<'d, T: Instance> { - _inner: PeripheralRef<'d, T>, + _inner: Peri<'d, T>, } impl<'d, T: Instance> OpAmp<'d, T> { /// Create a new driver instance. /// /// Does not enable the opamp, but does set the speed mode on some families. - pub fn new(opamp: impl Peripheral

+ 'd, #[cfg(opamp_g4)] speed: OpAmpSpeed) -> Self { - into_ref!(opamp); - + pub fn new(opamp: Peri<'d, T>, #[cfg(opamp_g4)] speed: OpAmpSpeed) -> Self { #[cfg(opamp_g4)] T::regs().csr().modify(|w| { - w.set_opahsm(speed.into()); + w.set_opahsm(speed == OpAmpSpeed::HighSpeed); }); Self { _inner: opamp } @@ -82,35 +88,81 @@ impl<'d, T: Instance> OpAmp<'d, T> { /// [`OpAmpOutput`] is dropped. pub fn buffer_ext( &mut self, - in_pin: impl Peripheral

+ crate::gpio::Pin>, - out_pin: impl Peripheral

+ crate::gpio::Pin>, - gain: OpAmpGain, + in_pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, + out_pin: Peri<'_, impl OutputPin + crate::gpio::Pin>, ) -> OpAmpOutput<'_, T> { - into_ref!(in_pin); - into_ref!(out_pin); 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), - OpAmpGain::Mul4 => (0b10, 0b01), - OpAmpGain::Mul8 => (0b10, 0b10), - OpAmpGain::Mul16 => (0b10, 0b11), - }; + #[cfg(opamp_g4)] + let vm_sel = VmSel::OUTPUT; + #[cfg(not(opamp_g4))] + let vm_sel = VmSel::from_bits(0b11); T::regs().csr().modify(|w| { w.set_vp_sel(VpSel::from_bits(in_pin.channel())); - w.set_vm_sel(VmSel::from_bits(vm_sel)); - w.set_pga_gain(PgaGain::from_bits(pga_gain)); + w.set_vm_sel(vm_sel); #[cfg(opamp_g4)] - w.set_opaintoen(Opaintoen::OUTPUTPIN); + w.set_opaintoen(false); w.set_opampen(true); }); OpAmpOutput { _inner: self } } + + /// Configure the OpAmp as a PGA for the provided input pin, + /// outputting to the provided output pin, and enable the opamp. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may subsequently be used for ADC or comparator inputs. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The `OpAmpOutput` can then be + /// directly used as an ADC input. The opamp will be disabled when the + /// [`OpAmpOutput`] is dropped. + pub fn pga_ext( + &mut self, + in_pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, + out_pin: Peri<'_, impl OutputPin + crate::gpio::Pin>, + gain: OpAmpGain, + ) -> OpAmpOutput<'_, T> { + in_pin.set_as_analog(); + out_pin.set_as_analog(); + + #[cfg(opamp_g4)] + let vm_sel = VmSel::PGA; + #[cfg(not(opamp_g4))] + let vm_sel = VmSel::from_bits(0b10); + + #[cfg(opamp_g4)] + let pga_gain = match gain { + OpAmpGain::Mul2 => PgaGain::GAIN2, + OpAmpGain::Mul4 => PgaGain::GAIN4, + OpAmpGain::Mul8 => PgaGain::GAIN8, + OpAmpGain::Mul16 => PgaGain::GAIN16, + OpAmpGain::Mul32 => PgaGain::GAIN32, + OpAmpGain::Mul64 => PgaGain::GAIN64, + }; + #[cfg(not(opamp_g4))] + let pga_gain = PgaGain::from_bits(match gain { + OpAmpGain::Mul2 => 0b00, + OpAmpGain::Mul4 => 0b01, + OpAmpGain::Mul8 => 0b10, + OpAmpGain::Mul16 => 0b11, + }); + + T::regs().csr().modify(|w| { + w.set_vp_sel(VpSel::from_bits(in_pin.channel())); + w.set_vm_sel(vm_sel); + w.set_pga_gain(pga_gain); + #[cfg(opamp_g4)] + w.set_opaintoen(false); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + /// Configure the OpAmp as a buffer for the DAC it is connected to, /// outputting to the provided output pin, and enable the opamp. /// @@ -119,11 +171,7 @@ impl<'d, T: Instance> OpAmp<'d, T> { /// directly used as an ADC input. The opamp will be disabled when the /// [`OpAmpOutput`] is dropped. #[cfg(opamp_g4)] - pub fn buffer_dac( - &mut self, - out_pin: impl Peripheral

+ crate::gpio::Pin>, - ) -> OpAmpOutput<'_, T> { - into_ref!(out_pin); + pub fn buffer_dac(&mut self, out_pin: Peri<'_, impl OutputPin + crate::gpio::Pin>) -> OpAmpOutput<'_, T> { out_pin.set_as_analog(); T::regs().csr().modify(|w| { @@ -131,7 +179,7 @@ impl<'d, T: Instance> OpAmp<'d, T> { w.set_vm_sel(VmSel::OUTPUT); w.set_vp_sel(VpSel::DAC3_CH1); - w.set_opaintoen(Opaintoen::OUTPUTPIN); + w.set_opaintoen(false); w.set_opampen(true); }); @@ -149,32 +197,259 @@ impl<'d, T: Instance> OpAmp<'d, T> { #[cfg(opamp_g4)] pub fn buffer_int( &mut self, - pin: impl Peripheral

+ crate::gpio::Pin>, - gain: OpAmpGain, + pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, ) -> OpAmpInternalOutput<'_, 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), - OpAmpGain::Mul4 => (0b10, 0b01), - OpAmpGain::Mul8 => (0b10, 0b10), - OpAmpGain::Mul16 => (0b10, 0b11), - }; - T::regs().csr().modify(|w| { - use crate::pac::opamp::vals::*; w.set_vp_sel(VpSel::from_bits(pin.channel())); - w.set_vm_sel(VmSel::from_bits(vm_sel)); - w.set_pga_gain(PgaGain::from_bits(pga_gain)); - w.set_opaintoen(Opaintoen::ADCCHANNEL); + w.set_vm_sel(VmSel::OUTPUT); + #[cfg(opamp_g4)] + w.set_opaintoen(true); w.set_opampen(true); }); OpAmpInternalOutput { _inner: self } } + + /// Configure the OpAmp as a PGA for the provided input pin, + /// with the output only used internally, and enable the opamp. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may be subsequently used for ADC or comparator inputs. + /// + /// The returned `OpAmpInternalOutput` struct may be used as an ADC input. + /// The opamp output will be disabled when it is dropped. + #[cfg(opamp_g4)] + pub fn pga_int( + &mut self, + pin: Peri<'_, impl NonInvertingPin + crate::gpio::Pin>, + gain: OpAmpGain, + ) -> OpAmpInternalOutput<'_, T> { + pin.set_as_analog(); + + let pga_gain = match gain { + OpAmpGain::Mul2 => PgaGain::GAIN2, + OpAmpGain::Mul4 => PgaGain::GAIN4, + OpAmpGain::Mul8 => PgaGain::GAIN8, + OpAmpGain::Mul16 => PgaGain::GAIN16, + OpAmpGain::Mul32 => PgaGain::GAIN32, + OpAmpGain::Mul64 => PgaGain::GAIN64, + }; + + T::regs().csr().modify(|w| { + w.set_vp_sel(VpSel::from_bits(pin.channel())); + w.set_vm_sel(VmSel::PGA); + w.set_pga_gain(pga_gain); + w.set_opaintoen(true); + w.set_opampen(true); + }); + + OpAmpInternalOutput { _inner: self } + } + + /// Configure the OpAmp as a standalone DAC with the inverting input + /// connected to the provided pin, and the output is connected + /// internally to an ADC channel. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may be subsequently used for ADC or comparator inputs. + /// + /// The returned `OpAmpInternalOutput` struct may be used as an ADC + /// input. The opamp output will be disabled when it is dropped. + #[cfg(opamp_g4)] + pub fn standalone_dac_int( + &mut self, + m_pin: Peri<'_, impl InvertingPin + crate::gpio::Pin>, + ) -> OpAmpInternalOutput<'_, T> { + m_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::DAC3_CH1); // Actually DAC3_CHx + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(true); + w.set_opampen(true); + }); + + OpAmpInternalOutput { _inner: self } + } + + /// Configure the OpAmp as a standalone DAC with the inverting input + /// connected to the provided pin, and the output connected to the + /// provided pin. + /// + /// The input pin is configured for analogue mode but not consumed, + /// so it may be subsequently used for ADC or comparator inputs. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The opamp will be disabled when + /// the [`OpAmpOutput`] is dropped. + #[cfg(opamp_g4)] + pub fn standalone_dac_ext( + &mut self, + m_pin: Peri<'_, impl InvertingPin + crate::gpio::Pin>, + out_pin: Peri<'_, impl OutputPin + crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { + m_pin.set_as_analog(); + out_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::DAC3_CH1); // Actually DAC3_CHx + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(false); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + + /// Configure the OpAmp in standalone mode with the non-inverting input + /// connected to the provided `p_pin`, the inverting input connected to + /// the `m_pin`, and output to the provided `out_pin`. + /// + /// The input pins are configured for analogue mode but not consumed, + /// allowing their subsequent use for ADC or comparator inputs. + /// + /// The output pin is held within the returned [`OpAmpOutput`] struct, + /// preventing it being used elsewhere. The opamp will be disabled when + /// the [`OpAmpOutput`] is dropped. + #[cfg(opamp_g4)] + pub fn standalone_ext( + &mut self, + p_pin: Peri<'d, impl NonInvertingPin + crate::gpio::Pin>, + m_pin: Peri<'d, impl InvertingPin + crate::gpio::Pin>, + out_pin: Peri<'d, impl OutputPin + crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { + p_pin.set_as_analog(); + m_pin.set_as_analog(); + out_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::from_bits(p_pin.channel())); + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(false); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + + /// Configure the OpAmp in standalone mode with the non-inverting input + /// connected to the provided `p_pin`, the inverting input connected to + /// the `m_pin`, and output is connected to the DAC. + /// + /// The input pins are configured for analogue mode but not consumed, + /// allowing their subsequent use for ADC or comparator inputs. + /// + /// The returned `OpAmpOutput` struct may be used as an ADC + /// input. The opamp output will be disabled when it is dropped. + #[cfg(opamp_g4)] + pub fn standalone_int( + &mut self, + p_pin: Peri<'d, impl NonInvertingPin + crate::gpio::Pin>, + m_pin: Peri<'d, impl InvertingPin + crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { + p_pin.set_as_analog(); + m_pin.set_as_analog(); + + T::regs().csr().modify(|w| { + use crate::pac::opamp::vals::*; + w.set_vp_sel(VpSel::from_bits(p_pin.channel())); + w.set_vm_sel(VmSel::from_bits(m_pin.channel())); + w.set_opaintoen(true); + w.set_opampen(true); + }); + + OpAmpOutput { _inner: self } + } + + /// Calibrates the operational amplifier. + /// + /// This function enables the opamp and sets the user trim mode for calibration. + /// Depending on the speed mode of the opamp, it calibrates the differential pair inputs. + /// For normal speed, both the P and N differential pairs are calibrated, + /// while for high-speed mode, only the P differential pair is calibrated. + /// + /// Calibrating a differential pair requires waiting 12ms in the worst case (binary method). + #[cfg(opamp_g4)] + pub fn calibrate(&mut self) { + T::regs().csr().modify(|w| { + w.set_opampen(true); + w.set_calon(true); + w.set_usertrim(true); + }); + + if T::regs().csr().read().opahsm() { + self.calibrate_differential_pair(OpAmpDifferentialPair::P); + } else { + self.calibrate_differential_pair(OpAmpDifferentialPair::P); + self.calibrate_differential_pair(OpAmpDifferentialPair::N); + } + + T::regs().csr().modify(|w| { + w.set_calon(false); + w.set_opampen(false); + }); + } + + /// Calibrate differential pair. + /// + /// The calibration is done by trying different offset values and + /// measuring the outcal bit. + /// + /// The calibration range is from 0 to 31. + /// + /// The result is stored in the OPAMP_CSR register. + #[cfg(opamp_g4)] + fn calibrate_differential_pair(&mut self, pair: OpAmpDifferentialPair) { + let mut low = 0; + let mut high = 31; + + let calsel = match pair { + OpAmpDifferentialPair::P => Calsel::PERCENT10, + OpAmpDifferentialPair::N => Calsel::PERCENT90, + }; + + T::regs().csr().modify(|w| { + w.set_calsel(calsel); + }); + + while low <= high { + let mid = (low + high) / 2; + + T::regs().csr().modify(|w| match pair { + OpAmpDifferentialPair::P => { + #[cfg(feature = "defmt")] + defmt::debug!("opamp p calibration. offset: {}", mid); + w.set_trimoffsetp(mid); + } + OpAmpDifferentialPair::N => { + #[cfg(feature = "defmt")] + defmt::debug!("opamp n calibration. offset: {}", mid); + w.set_trimoffsetn(mid); + } + }); + + // The closer the trimming value is to the optimum trimming value, the longer it takes to stabilize + // (with a maximum stabilization time remaining below 2 ms in any case) -- RM0440 25.3.7 + blocking_delay_ms(2); + + if !T::regs().csr().read().calout() { + if mid == 0 { + break; + } + high = mid - 1; + } else { + if mid == 31 { + break; + } + low = mid + 1; + } + } + } } impl<'d, T: Instance> Drop for OpAmpOutput<'d, T> { @@ -211,7 +486,7 @@ pub(crate) trait SealedOutputPin {} /// Opamp instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + 'static {} +pub trait Instance: SealedInstance + PeripheralType + 'static {} /// Non-inverting pin trait. #[allow(private_bounds)] pub trait NonInvertingPin: SealedNonInvertingPin {} @@ -251,10 +526,12 @@ foreach_peripheral!( impl_opamp_external_output!(OPAMP2, ADC2, 3); }; (opamp, OPAMP3) => { + impl_opamp_external_output!(OPAMP3, ADC1, 12); impl_opamp_external_output!(OPAMP3, ADC3, 1); }; // OPAMP4 only in STM32G4 Cat 3 devices (opamp, OPAMP4) => { + impl_opamp_external_output!(OPAMP4, ADC1, 11); impl_opamp_external_output!(OPAMP4, ADC4, 3); }; // OPAMP5 only in STM32G4 Cat 3 devices @@ -264,6 +541,7 @@ foreach_peripheral!( // OPAMP6 only in STM32G4 Cat 3/4 devices (opamp, OPAMP6) => { impl_opamp_external_output!(OPAMP6, ADC1, 14); + impl_opamp_external_output!(OPAMP6, ADC2, 14); }; ); @@ -344,6 +622,18 @@ macro_rules! impl_opamp_vp_pin { }; } +#[allow(unused_macros)] +macro_rules! impl_opamp_vn_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl crate::opamp::InvertingPin for crate::peripherals::$pin {} + impl crate::opamp::SealedInvertingPin for crate::peripherals::$pin { + fn channel(&self) -> u8 { + $ch + } + } + }; +} + #[allow(unused_macros)] macro_rules! impl_opamp_vout_pin { ($inst:ident, $pin:ident) => { diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index 289bfa672..74edfd5e4 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -8,7 +8,7 @@ pub mod enums; use core::marker::PhantomData; use embassy_embedded_hal::{GetConfig, SetConfig}; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; pub use enums::*; use stm32_metapac::octospi::vals::{PhaseMode, SizeInBits}; @@ -16,8 +16,10 @@ use crate::dma::{word, ChannelAndRequest}; use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::mode::{Async, Blocking, Mode as PeriMode}; use crate::pac::octospi::{vals, Octospi as Regs}; +#[cfg(octospim_v1)] +use crate::pac::octospim::Octospim; use crate::rcc::{self, RccPeripheral}; -use crate::{peripherals, Peripheral}; +use crate::{peripherals, Peri}; /// OPSI driver config. #[derive(Clone, Copy)] @@ -50,7 +52,7 @@ pub struct Config { /// Enables the transaction boundary feature and defines the boundary to release /// the chip select pub chip_select_boundary: u8, - /// Enbales the delay block bypass so the sampling is not affected by the delay block + /// Enables the delay block bypass so the sampling is not affected by the delay block pub delay_block_bypass: bool, /// Enables communication regulation feature. Chip select is released when the other /// OctoSpi requests access to the bus @@ -158,18 +160,18 @@ pub enum OspiError { /// OSPI driver. pub struct Ospi<'d, T: Instance, M: PeriMode> { - _peri: PeripheralRef<'d, T>, - sck: Option>, - d0: Option>, - d1: Option>, - d2: Option>, - d3: Option>, - d4: Option>, - d5: Option>, - d6: Option>, - d7: Option>, - nss: Option>, - dqs: Option>, + _peri: Peri<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + nss: Option>, + dqs: Option>, dma: Option>, _phantom: PhantomData, config: Config, @@ -177,25 +179,165 @@ pub struct Ospi<'d, T: Instance, M: PeriMode> { } impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { + /// Enter memory mode. + /// The Input `read_config` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), OspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + + let reg = T::REGS; + while reg.sr().read().busy() {} + + reg.ccr().modify(|r| { + r.set_dqse(false); + r.set_sioo(true); + }); + + // Set wrting configurations, there are separate registers for write configurations in memory mapped mode + reg.wccr().modify(|w| { + w.set_imode(PhaseMode::from_bits(write_config.iwidth.into())); + w.set_idtr(write_config.idtr); + w.set_isize(SizeInBits::from_bits(write_config.isize.into())); + + w.set_admode(PhaseMode::from_bits(write_config.adwidth.into())); + w.set_addtr(write_config.idtr); + w.set_adsize(SizeInBits::from_bits(write_config.adsize.into())); + + w.set_dmode(PhaseMode::from_bits(write_config.dwidth.into())); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(PhaseMode::from_bits(write_config.abwidth.into())); + w.set_dqse(true); + }); + + reg.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::MEMORY_MAPPED); + r.set_tcen(false); + }); + Ok(()) + } + + /// Quit from memory mapped mode + pub fn disable_memory_mapped_mode(&mut self) { + let reg = T::REGS; + + reg.cr().modify(|r| { + r.set_fmode(crate::ospi::vals::FunctionalMode::INDIRECT_WRITE); + r.set_abort(true); + r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + reg.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable ospi + reg.cr().modify(|r| { + r.set_en(true); + }); + } + fn new_inner( - peri: impl Peripheral

+ 'd, - d0: Option>, - d1: Option>, - d2: Option>, - d3: Option>, - d4: Option>, - d5: Option>, - d6: Option>, - d7: Option>, - sck: Option>, - nss: Option>, - dqs: Option>, + peri: Peri<'d, T>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + sck: Option>, + nss: Option>, + dqs: Option>, dma: Option>, config: Config, width: OspiWidth, dual_quad: bool, ) -> Self { - into_ref!(peri); + #[cfg(octospim_v1)] + { + // RCC for octospim should be enabled before writing register + #[cfg(stm32l4)] + crate::pac::RCC.ahb2smenr().modify(|w| w.set_octospimsmen(true)); + #[cfg(stm32u5)] + crate::pac::RCC.ahb2enr1().modify(|w| w.set_octospimen(true)); + #[cfg(not(any(stm32l4, stm32u5)))] + crate::pac::RCC.ahb3enr().modify(|w| w.set_iomngren(true)); + + // Disable OctoSPI peripheral first + T::REGS.cr().modify(|w| { + w.set_en(false); + }); + + // OctoSPI IO Manager has been enabled before + T::OCTOSPIM_REGS.cr().modify(|w| { + w.set_muxen(false); + w.set_req2ack_time(0xff); + }); + + // Clear config + T::OCTOSPIM_REGS.p1cr().modify(|w| { + w.set_clksrc(false); + w.set_dqssrc(false); + w.set_ncssrc(false); + w.set_clken(false); + w.set_dqsen(false); + w.set_ncsen(false); + w.set_iolsrc(0); + w.set_iohsrc(0); + }); + + T::OCTOSPIM_REGS.p1cr().modify(|w| { + let octospi_src = if T::OCTOSPI_IDX == 1 { false } else { true }; + w.set_ncsen(true); + w.set_ncssrc(octospi_src); + w.set_clken(true); + w.set_clksrc(octospi_src); + if dqs.is_some() { + w.set_dqsen(true); + w.set_dqssrc(octospi_src); + } + + // Set OCTOSPIM IOL and IOH according to the index of OCTOSPI instance + if T::OCTOSPI_IDX == 1 { + w.set_iolen(true); + w.set_iolsrc(0); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b01); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b00); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } else { + w.set_iolen(true); + w.set_iolsrc(0b10); + // Enable IOH in octo and dual quad mode + if let OspiWidth::OCTO = width { + w.set_iohen(true); + w.set_iohsrc(0b11); + } else if dual_quad { + w.set_iohen(true); + w.set_iohsrc(0b10); + } else { + w.set_iohen(false); + w.set_iohsrc(0b00); + } + } + }); + } // System configuration rcc::enable_and_reset::(); @@ -228,7 +370,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { }); T::REGS.cr().modify(|w| { - w.set_fthres(vals::Threshold(config.fifo_threshold.into())); + w.set_fthres(vals::Threshold::from_bits(config.fifo_threshold.into())); }); // Wait for busy flag to clear @@ -244,7 +386,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { T::REGS.tcr().modify(|w| { w.set_sshift(match config.sample_shifting { - true => vals::SampleShift::HALFCYCLE, + true => vals::SampleShift::HALF_CYCLE, false => vals::SampleShift::NONE, }); w.set_dhqc(config.delay_hold_quarter_cycle); @@ -376,7 +518,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { } /// Function used to control or configure the target device without data transfer - pub async fn command(&mut self, command: &TransferConfig) -> Result<(), OspiError> { + pub fn blocking_command(&mut self, command: &TransferConfig) -> Result<(), OspiError> { // Wait for peripheral to be free while T::REGS.sr().read().busy() {} @@ -412,7 +554,9 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { let current_instruction = T::REGS.ir().read().instruction(); // For a indirect read transaction, the transaction begins when the instruction/address is set - T::REGS.cr().modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTREAD)); + T::REGS + .cr() + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_READ)); if T::REGS.ccr().read().admode() == vals::PhaseMode::NONE { T::REGS.ir().write(|v| v.set_instruction(current_instruction)); } else { @@ -447,7 +591,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { T::REGS .cr() - .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTWRITE)); + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_WRITE)); for idx in 0..buf.len() { while !T::REGS.sr().read().ftf() {} @@ -497,7 +641,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { }); T::REGS.cr().modify(|w| { - w.set_fthres(vals::Threshold(config.fifo_threshold.into())); + w.set_fthres(vals::Threshold::from_bits(config.fifo_threshold.into())); }); // Wait for busy flag to clear @@ -509,7 +653,7 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { T::REGS.tcr().modify(|w| { w.set_sshift(match config.sample_shifting { - true => vals::SampleShift::HALFCYCLE, + true => vals::SampleShift::HALF_CYCLE, false => vals::SampleShift::NONE, }); w.set_dhqc(config.delay_hold_quarter_cycle); @@ -539,11 +683,11 @@ impl<'d, T: Instance, M: PeriMode> Ospi<'d, T, M> { impl<'d, T: Instance> Ospi<'d, T, Blocking> { /// Create new blocking OSPI driver for a single spi external chip pub fn new_blocking_singlespi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + nss: Peri<'d, impl NSSPin>, config: Config, ) -> Self { Self::new_inner( @@ -571,11 +715,11 @@ impl<'d, T: Instance> Ospi<'d, T, Blocking> { /// Create new blocking OSPI driver for a dualspi external chip pub fn new_blocking_dualspi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + nss: Peri<'d, impl NSSPin>, config: Config, ) -> Self { Self::new_inner( @@ -603,13 +747,13 @@ impl<'d, T: Instance> Ospi<'d, T, Blocking> { /// Create new blocking OSPI driver for a quadspi external chip pub fn new_blocking_quadspi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + nss: Peri<'d, impl NSSPin>, config: Config, ) -> Self { Self::new_inner( @@ -637,17 +781,17 @@ impl<'d, T: Instance> Ospi<'d, T, Blocking> { /// Create new blocking OSPI driver for two quadspi external chips pub fn new_blocking_dualquadspi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + nss: Peri<'d, impl NSSPin>, config: Config, ) -> Self { Self::new_inner( @@ -675,17 +819,17 @@ impl<'d, T: Instance> Ospi<'d, T, Blocking> { /// Create new blocking OSPI driver for octospi external chips pub fn new_blocking_octospi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + nss: Peri<'d, impl NSSPin>, config: Config, ) -> Self { Self::new_inner( @@ -715,12 +859,12 @@ impl<'d, T: Instance> Ospi<'d, T, Blocking> { impl<'d, T: Instance> Ospi<'d, T, Async> { /// Create new blocking OSPI driver for a single spi external chip pub fn new_singlespi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + nss: Peri<'d, impl NSSPin>, + dma: Peri<'d, impl OctoDma>, config: Config, ) -> Self { Self::new_inner( @@ -748,12 +892,12 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { /// Create new blocking OSPI driver for a dualspi external chip pub fn new_dualspi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + nss: Peri<'d, impl NSSPin>, + dma: Peri<'d, impl OctoDma>, config: Config, ) -> Self { Self::new_inner( @@ -781,14 +925,14 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { /// Create new blocking OSPI driver for a quadspi external chip pub fn new_quadspi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + nss: Peri<'d, impl NSSPin>, + dma: Peri<'d, impl OctoDma>, config: Config, ) -> Self { Self::new_inner( @@ -816,18 +960,18 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { /// Create new blocking OSPI driver for two quadspi external chips pub fn new_dualquadspi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + nss: Peri<'d, impl NSSPin>, + dma: Peri<'d, impl OctoDma>, config: Config, ) -> Self { Self::new_inner( @@ -855,18 +999,18 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { /// Create new blocking OSPI driver for octospi external chips pub fn new_octospi( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - d4: impl Peripheral

> + 'd, - d5: impl Peripheral

> + 'd, - d6: impl Peripheral

> + 'd, - d7: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + nss: Peri<'d, impl NSSPin>, + dma: Peri<'d, impl OctoDma>, config: Config, ) -> Self { Self::new_inner( @@ -907,7 +1051,9 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { let current_instruction = T::REGS.ir().read().instruction(); // For a indirect read transaction, the transaction begins when the instruction/address is set - T::REGS.cr().modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTREAD)); + T::REGS + .cr() + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_READ)); if T::REGS.ccr().read().admode() == vals::PhaseMode::NONE { T::REGS.ir().write(|v| v.set_instruction(current_instruction)); } else { @@ -942,7 +1088,7 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { self.configure_command(&transaction, Some(buf.len()))?; T::REGS .cr() - .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTWRITE)); + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_WRITE)); let transfer = unsafe { self.dma @@ -975,7 +1121,9 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { let current_instruction = T::REGS.ir().read().instruction(); // For a indirect read transaction, the transaction begins when the instruction/address is set - T::REGS.cr().modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTREAD)); + T::REGS + .cr() + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_READ)); if T::REGS.ccr().read().admode() == vals::PhaseMode::NONE { T::REGS.ir().write(|v| v.set_instruction(current_instruction)); } else { @@ -1010,7 +1158,7 @@ impl<'d, T: Instance> Ospi<'d, T, Async> { self.configure_command(&transaction, Some(buf.len()))?; T::REGS .cr() - .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECTWRITE)); + .modify(|v| v.set_fmode(vals::FunctionalMode::INDIRECT_WRITE)); let transfer = unsafe { self.dma @@ -1056,13 +1204,27 @@ fn finish_dma(regs: Regs) { }); } +#[cfg(octospim_v1)] +/// OctoSPI I/O manager instance trait. +pub(crate) trait SealedOctospimInstance { + const OCTOSPIM_REGS: Octospim; + const OCTOSPI_IDX: u8; +} + +/// OctoSPI instance trait. pub(crate) trait SealedInstance { const REGS: Regs; } /// OSPI instance trait. +#[cfg(octospim_v1)] #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + SealedOctospimInstance {} + +/// OSPI instance trait. +#[cfg(not(octospim_v1))] +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral {} pin_trait!(SckPin, Instance); pin_trait!(NckPin, Instance); @@ -1078,6 +1240,31 @@ pin_trait!(DQSPin, Instance); pin_trait!(NSSPin, Instance); dma_trait!(OctoDma, Instance); +// Hard-coded the octospi index, for OCTOSPIM +#[cfg(octospim_v1)] +impl SealedOctospimInstance for peripherals::OCTOSPI1 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 1; +} + +#[cfg(all(octospim_v1, peri_octospi2))] +impl SealedOctospimInstance for peripherals::OCTOSPI2 { + const OCTOSPIM_REGS: Octospim = crate::pac::OCTOSPIM; + const OCTOSPI_IDX: u8 = 2; +} + +#[cfg(octospim_v1)] +foreach_peripheral!( + (octospi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +#[cfg(not(octospim_v1))] foreach_peripheral!( (octospi, $inst:ident) => { impl SealedInstance for peripherals::$inst { diff --git a/embassy-stm32/src/qspi/enums.rs b/embassy-stm32/src/qspi/enums.rs index ecade9b1a..9ec4c1b43 100644 --- a/embassy-stm32/src/qspi/enums.rs +++ b/embassy-stm32/src/qspi/enums.rs @@ -9,9 +9,9 @@ pub(crate) enum QspiMode { MemoryMapped, } -impl Into for QspiMode { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(val: QspiMode) -> Self { + match val { QspiMode::IndirectWrite => 0b00, QspiMode::IndirectRead => 0b01, QspiMode::AutoPolling => 0b10, @@ -34,9 +34,9 @@ pub enum QspiWidth { QUAD, } -impl Into for QspiWidth { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(val: QspiWidth) -> Self { + match val { QspiWidth::NONE => 0b00, QspiWidth::SING => 0b01, QspiWidth::DUAL => 0b10, @@ -55,9 +55,9 @@ pub enum FlashSelection { Flash2, } -impl Into for FlashSelection { - fn into(self) -> bool { - match self { +impl From for bool { + fn from(val: FlashSelection) -> Self { + match val { FlashSelection::Flash1 => false, FlashSelection::Flash2 => true, } @@ -94,9 +94,9 @@ pub enum MemorySize { Other(u8), } -impl Into for MemorySize { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(val: MemorySize) -> Self { + match val { MemorySize::_1KiB => 9, MemorySize::_2KiB => 10, MemorySize::_4KiB => 11, @@ -138,9 +138,9 @@ pub enum AddressSize { _32bit, } -impl Into for AddressSize { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(val: AddressSize) -> Self { + match val { AddressSize::_8Bit => 0b00, AddressSize::_16Bit => 0b01, AddressSize::_24bit => 0b10, @@ -163,9 +163,9 @@ pub enum ChipSelectHighTime { _8Cycle, } -impl Into for ChipSelectHighTime { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(val: ChipSelectHighTime) -> Self { + match val { ChipSelectHighTime::_1Cycle => 0, ChipSelectHighTime::_2Cycle => 1, ChipSelectHighTime::_3Cycle => 2, @@ -216,9 +216,9 @@ pub enum FIFOThresholdLevel { _32Bytes, } -impl Into for FIFOThresholdLevel { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(val: FIFOThresholdLevel) -> Self { + match val { FIFOThresholdLevel::_1Bytes => 0, FIFOThresholdLevel::_2Bytes => 1, FIFOThresholdLevel::_3Bytes => 2, @@ -293,9 +293,9 @@ pub enum DummyCycles { _31, } -impl Into for DummyCycles { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(val: DummyCycles) -> Self { + match val { DummyCycles::_0 => 0, DummyCycles::_1 => 1, DummyCycles::_2 => 2, diff --git a/embassy-stm32/src/qspi/mod.rs b/embassy-stm32/src/qspi/mod.rs index 308947e99..0df057c53 100644 --- a/embassy-stm32/src/qspi/mod.rs +++ b/embassy-stm32/src/qspi/mod.rs @@ -6,7 +6,7 @@ pub mod enums; use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; use enums::*; use crate::dma::ChannelAndRequest; @@ -14,11 +14,11 @@ use crate::gpio::{AfType, AnyPin, OutputType, Pull, Speed}; use crate::mode::{Async, Blocking, Mode as PeriMode}; use crate::pac::quadspi::Quadspi as Regs; use crate::rcc::{self, RccPeripheral}; -use crate::{peripherals, Peripheral}; +use crate::{peripherals, Peri}; /// QSPI transfer configuration. pub struct TransferConfig { - /// Instraction width (IMODE) + /// Instruction width (IMODE) pub iwidth: QspiWidth, /// Address width (ADMODE) pub awidth: QspiWidth, @@ -75,13 +75,13 @@ impl Default for Config { /// QSPI driver. #[allow(dead_code)] pub struct Qspi<'d, T: Instance, M: PeriMode> { - _peri: PeripheralRef<'d, T>, - sck: Option>, - d0: Option>, - d1: Option>, - d2: Option>, - d3: Option>, - nss: Option>, + _peri: Peri<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + nss: Option>, dma: Option>, _phantom: PhantomData, config: Config, @@ -89,19 +89,17 @@ pub struct Qspi<'d, T: Instance, M: PeriMode> { impl<'d, T: Instance, M: PeriMode> Qspi<'d, T, M> { fn new_inner( - peri: impl Peripheral

+ 'd, - d0: Option>, - d1: Option>, - d2: Option>, - d3: Option>, - sck: Option>, - nss: Option>, + peri: Peri<'d, T>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + sck: Option>, + nss: Option>, dma: Option>, config: Config, fsel: FlashSelection, ) -> Self { - into_ref!(peri); - rcc::enable_and_reset::(); while T::REGS.sr().read().busy() {} @@ -148,7 +146,7 @@ impl<'d, T: Instance, M: PeriMode> Qspi<'d, T, M> { } /// Do a QSPI command. - pub fn command(&mut self, transaction: TransferConfig) { + pub fn blocking_command(&mut self, transaction: TransferConfig) { #[cfg(not(stm32h7))] T::REGS.cr().modify(|v| v.set_dmaen(false)); self.setup_transaction(QspiMode::IndirectWrite, &transaction, None); @@ -172,7 +170,7 @@ impl<'d, T: Instance, M: PeriMode> Qspi<'d, T, M> { }); for b in buf { - while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + while !T::REGS.sr().read().tcf() && (T::REGS.sr().read().flevel() == 0) {} *b = unsafe { (T::REGS.dr().as_ptr() as *mut u8).read_volatile() }; } @@ -201,7 +199,42 @@ impl<'d, T: Instance, M: PeriMode> Qspi<'d, T, M> { T::REGS.fcr().modify(|v| v.set_ctcf(true)); } + /// Enable memory map mode + pub fn enable_memory_map(&mut self, transaction: &TransferConfig) { + T::REGS.fcr().modify(|v| { + v.set_csmf(true); + v.set_ctcf(true); + v.set_ctef(true); + v.set_ctof(true); + }); + T::REGS.ccr().write(|v| { + v.set_fmode(QspiMode::MemoryMapped.into()); + v.set_imode(transaction.iwidth.into()); + v.set_instruction(transaction.instruction); + v.set_admode(transaction.awidth.into()); + v.set_adsize(self.config.address_size.into()); + v.set_dmode(transaction.dwidth.into()); + v.set_abmode(QspiWidth::NONE.into()); + v.set_dcyc(transaction.dummy.into()); + }); + } + fn setup_transaction(&mut self, fmode: QspiMode, transaction: &TransferConfig, data_len: Option) { + match (transaction.address, transaction.awidth) { + (Some(_), QspiWidth::NONE) => panic!("QSPI address can't be sent with an address width of NONE"), + (Some(_), _) => {} + (None, QspiWidth::NONE) => {} + (None, _) => panic!("QSPI address is not set, so the address width should be NONE"), + } + + match (data_len, transaction.dwidth) { + (Some(0), _) => panic!("QSPI data must be at least one byte"), + (Some(_), QspiWidth::NONE) => panic!("QSPI data can't be sent with a data width of NONE"), + (Some(_), _) => {} + (None, QspiWidth::NONE) => {} + (None, _) => panic!("QSPI data is empty, so the data width should be NONE"), + } + T::REGS.fcr().modify(|v| { v.set_csmf(true); v.set_ctcf(true); @@ -237,13 +270,13 @@ impl<'d, T: Instance, M: PeriMode> Qspi<'d, T, M> { impl<'d, T: Instance> Qspi<'d, T, Blocking> { /// Create a new QSPI driver for bank 1, in blocking mode. pub fn new_blocking_bank1( - peri: impl Peripheral

+ 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - sck: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + d0: Peri<'d, impl BK1D0Pin>, + d1: Peri<'d, impl BK1D1Pin>, + d2: Peri<'d, impl BK1D2Pin>, + d3: Peri<'d, impl BK1D3Pin>, + sck: Peri<'d, impl SckPin>, + nss: Peri<'d, impl BK1NSSPin>, config: Config, ) -> Self { Self::new_inner( @@ -265,13 +298,13 @@ impl<'d, T: Instance> Qspi<'d, T, Blocking> { /// Create a new QSPI driver for bank 2, in blocking mode. pub fn new_blocking_bank2( - peri: impl Peripheral

+ 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - sck: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + d0: Peri<'d, impl BK2D0Pin>, + d1: Peri<'d, impl BK2D1Pin>, + d2: Peri<'d, impl BK2D2Pin>, + d3: Peri<'d, impl BK2D3Pin>, + sck: Peri<'d, impl SckPin>, + nss: Peri<'d, impl BK2NSSPin>, config: Config, ) -> Self { Self::new_inner( @@ -295,14 +328,14 @@ impl<'d, T: Instance> Qspi<'d, T, Blocking> { impl<'d, T: Instance> Qspi<'d, T, Async> { /// Create a new QSPI driver for bank 1. pub fn new_bank1( - peri: impl Peripheral

+ 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - sck: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + d0: Peri<'d, impl BK1D0Pin>, + d1: Peri<'d, impl BK1D1Pin>, + d2: Peri<'d, impl BK1D2Pin>, + d3: Peri<'d, impl BK1D3Pin>, + sck: Peri<'d, impl SckPin>, + nss: Peri<'d, impl BK1NSSPin>, + dma: Peri<'d, impl QuadDma>, config: Config, ) -> Self { Self::new_inner( @@ -324,14 +357,14 @@ impl<'d, T: Instance> Qspi<'d, T, Async> { /// Create a new QSPI driver for bank 2. pub fn new_bank2( - peri: impl Peripheral

+ 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, - sck: impl Peripheral

> + 'd, - nss: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + d0: Peri<'d, impl BK2D0Pin>, + d1: Peri<'d, impl BK2D1Pin>, + d2: Peri<'d, impl BK2D2Pin>, + d3: Peri<'d, impl BK2D3Pin>, + sck: Peri<'d, impl SckPin>, + nss: Peri<'d, impl BK2NSSPin>, + dma: Peri<'d, impl QuadDma>, config: Config, ) -> Self { Self::new_inner( @@ -353,6 +386,21 @@ impl<'d, T: Instance> Qspi<'d, T, Async> { /// Blocking read data, using DMA. pub fn blocking_read_dma(&mut self, buf: &mut [u8], transaction: TransferConfig) { + let transfer = self.start_read_transfer(transaction, buf); + transfer.blocking_wait(); + } + + /// Async read data, using DMA. + pub async fn read_dma(&mut self, buf: &mut [u8], transaction: TransferConfig) { + let transfer = self.start_read_transfer(transaction, buf); + transfer.await; + } + + fn start_read_transfer<'a>( + &'a mut self, + transaction: TransferConfig, + buf: &'a mut [u8], + ) -> crate::dma::Transfer<'a> { self.setup_transaction(QspiMode::IndirectWrite, &transaction, Some(buf.len())); T::REGS.ccr().modify(|v| { @@ -373,12 +421,22 @@ impl<'d, T: Instance> Qspi<'d, T, Async> { // STM32H7 does not have dmaen #[cfg(not(stm32h7))] T::REGS.cr().modify(|v| v.set_dmaen(true)); - - transfer.blocking_wait(); + transfer } /// Blocking write data, using DMA. pub fn blocking_write_dma(&mut self, buf: &[u8], transaction: TransferConfig) { + let transfer = self.start_write_transfer(transaction, buf); + transfer.blocking_wait(); + } + + /// Async write data, using DMA. + pub async fn write_dma(&mut self, buf: &[u8], transaction: TransferConfig) { + let transfer = self.start_write_transfer(transaction, buf); + transfer.await; + } + + fn start_write_transfer<'a>(&'a mut self, transaction: TransferConfig, buf: &'a [u8]) -> crate::dma::Transfer<'a> { self.setup_transaction(QspiMode::IndirectWrite, &transaction, Some(buf.len())); T::REGS.ccr().modify(|v| { @@ -395,8 +453,7 @@ impl<'d, T: Instance> Qspi<'d, T, Async> { // STM32H7 does not have dmaen #[cfg(not(stm32h7))] T::REGS.cr().modify(|v| v.set_dmaen(true)); - - transfer.blocking_wait(); + transfer } } @@ -406,7 +463,7 @@ trait SealedInstance { /// QSPI instance trait. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral {} pin_trait!(SckPin, Instance); pin_trait!(BK1D0Pin, Instance); diff --git a/embassy-stm32/src/rcc/bd.rs b/embassy-stm32/src/rcc/bd.rs index 4e9c18594..e2c704405 100644 --- a/embassy-stm32/src/rcc/bd.rs +++ b/embassy-stm32/src/rcc/bd.rs @@ -16,9 +16,13 @@ pub enum LseMode { Bypass, } +#[derive(Clone, Copy)] pub struct LseConfig { pub frequency: Hertz, pub mode: LseMode, + /// If peripherals other than RTC/TAMP or RCC functions need the lse this bit must be set + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] + pub peripherals_clocked: bool, } #[allow(dead_code)] @@ -41,8 +45,8 @@ impl From for crate::pac::rcc::vals::Lsedrv { match value { #[cfg(not(stm32h5))] // ES0565: LSE Low drive mode is not functional LseDrive::Low => Lsedrv::LOW, - LseDrive::MediumLow => Lsedrv::MEDIUMLOW, - LseDrive::MediumHigh => Lsedrv::MEDIUMHIGH, + LseDrive::MediumLow => Lsedrv::MEDIUM_LOW, + LseDrive::MediumHigh => Lsedrv::MEDIUM_HIGH, LseDrive::High => Lsedrv::HIGH, } } @@ -80,6 +84,7 @@ fn bdcr() -> Reg { return crate::pac::RCC.csr1(); } +#[derive(Clone, Copy)] pub struct LsConfig { pub rtc: RtcClockSource, pub lsi: bool, @@ -87,12 +92,25 @@ pub struct LsConfig { } impl LsConfig { + /// Creates an [`LsConfig`] using the LSI when possible. + pub const fn new() -> Self { + // on L5, just the fact that LSI is enabled makes things crash. + // TODO: investigate. + + #[cfg(not(stm32l5))] + return Self::default_lsi(); + #[cfg(stm32l5)] + return Self::off(); + } + pub const fn default_lse() -> Self { Self { rtc: RtcClockSource::LSE, lse: Some(LseConfig { frequency: Hertz(32_768), mode: LseMode::Oscillator(LseDrive::MediumHigh), + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] + peripherals_clocked: false, }), lsi: false, } @@ -117,13 +135,7 @@ impl LsConfig { impl Default for LsConfig { fn default() -> Self { - // on L5, just the fact that LSI is enabled makes things crash. - // TODO: investigate. - - #[cfg(not(stm32l5))] - return Self::default_lsi(); - #[cfg(stm32l5)] - return Self::off(); + Self::new() } } @@ -146,6 +158,15 @@ impl LsConfig { }, None => (false, false, None), }; + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba))] + let lse_sysen = if let Some(lse) = self.lse { + Some(lse.peripherals_clocked) + } else { + None + }; + #[cfg(rcc_u0)] + let lse_sysen = Some(lse_en); + _ = lse_drv; // not all chips have it. // Disable backup domain write protection @@ -186,6 +207,10 @@ impl LsConfig { } ok &= reg.lseon() == lse_en; ok &= reg.lsebyp() == lse_byp; + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba, rcc_u0))] + if let Some(lse_sysen) = lse_sysen { + ok &= reg.lsesysen() == lse_sysen; + } #[cfg(not(any(rcc_f1, rcc_f1cl, rcc_f100, rcc_f2, rcc_f4, rcc_f410, rcc_l1)))] if let Some(lse_drv) = lse_drv { ok &= reg.lsedrv() == lse_drv.into(); @@ -233,6 +258,17 @@ impl LsConfig { }); while !bdcr().read().lserdy() {} + + #[cfg(any(rcc_l5, rcc_u5, rcc_wle, rcc_wl5, rcc_wba, rcc_u0))] + if let Some(lse_sysen) = lse_sysen { + bdcr().modify(|w| { + w.set_lsesysen(lse_sysen); + }); + + if lse_sysen { + while !bdcr().read().lsesysrdy() {} + } + } } if self.rtc != RtcClockSource::DISABLE { diff --git a/embassy-stm32/src/rcc/c0.rs b/embassy-stm32/src/rcc/c0.rs index 5adf37941..cac2a9149 100644 --- a/embassy-stm32/src/rcc/c0.rs +++ b/embassy-stm32/src/rcc/c0.rs @@ -3,6 +3,7 @@ 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::LSI_FREQ; use crate::time::Hertz; /// HSI speed @@ -37,6 +38,7 @@ pub struct Hsi { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { /// HSI Configuration pub hsi: Option, @@ -57,9 +59,8 @@ pub struct Config { pub mux: super::mux::ClockMux, } -impl Default for Config { - #[inline] - fn default() -> Config { +impl Config { + pub const fn new() -> Self { Config { hsi: Some(Hsi { sys_div: HsiSysDiv::DIV4, @@ -69,12 +70,18 @@ impl Default for Config { sys: Sysclk::HSISYS, ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, - ls: Default::default(), - mux: Default::default(), + ls: crate::rcc::LsConfig::new(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Config { + Self::new() + } +} + pub(crate) unsafe fn init(config: Config) { // Turn on the HSI match config.hsi { @@ -109,8 +116,8 @@ pub(crate) unsafe fn init(config: Config) { } Some(hse) => { match hse.mode { - HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), - HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), } RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); @@ -120,20 +127,27 @@ pub(crate) unsafe fn init(config: Config) { } }; + let rtc = config.ls.init(); + let sys = match config.sys { Sysclk::HSISYS => unwrap!(hsisys), Sysclk::HSE => unwrap!(hse), + Sysclk::LSI => { + assert!(config.ls.lsi); + LSI_FREQ + } + Sysclk::LSE => unwrap!(config.ls.lse).frequency, _ => unreachable!(), }; - assert!(max::SYSCLK.contains(&sys)); + rcc_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)); + rcc_assert!(max::HCLK.contains(&hclk)); let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); - assert!(max::PCLK.contains(&pclk1)); + rcc_assert!(max::PCLK.contains(&pclk1)); let latency = match hclk.0 { ..=24_000_000 => Latency::WS0, @@ -161,8 +175,6 @@ pub(crate) unsafe fn init(config: Config) { RCC.cr().modify(|w| w.set_hsion(false)); } - let rtc = config.ls.init(); - config.mux.init(); set_clocks!( @@ -179,6 +191,9 @@ pub(crate) unsafe fn init(config: Config) { lsi: None, lse: None, ); + + RCC.ccipr() + .modify(|w| w.set_adc1sel(stm32_metapac::rcc::vals::Adcsel::SYS)); } mod max { diff --git a/embassy-stm32/src/rcc/f013.rs b/embassy-stm32/src/rcc/f013.rs index 63dc27bdd..1155b6acd 100644 --- a/embassy-stm32/src/rcc/f013.rs +++ b/embassy-stm32/src/rcc/f013.rs @@ -4,11 +4,13 @@ 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)] +#[cfg(all(stm32f1, not(stm32f107)))] 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}; +#[cfg(stm32f107)] +pub use crate::pac::rcc::vals::{I2s2src, Pll2mul as Pll2Mul, Prediv1 as PllPreDiv, Prediv1src, Usbpre as UsbPre}; use crate::pac::{FLASH, RCC}; use crate::time::Hertz; @@ -37,6 +39,8 @@ pub enum PllSource { HSI, #[cfg(rcc_f0v4)] HSI48, + #[cfg(stm32f107)] + PLL2, } #[derive(Clone, Copy)] @@ -52,6 +56,12 @@ pub struct Pll { pub mul: PllMul, } +#[cfg(stm32f107)] +#[derive(Clone, Copy)] +pub struct Pll2Or3 { + pub mul: Pll2Mul, +} + #[cfg(all(stm32f3, not(rcc_f37)))] #[derive(Clone, Copy)] pub enum AdcClockSource { @@ -76,6 +86,7 @@ pub enum HrtimClockSource { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { pub hsi: bool, pub hse: Option, @@ -84,6 +95,12 @@ pub struct Config { pub sys: Sysclk, pub pll: Option, + #[cfg(stm32f107)] + pub pll2: Option, + #[cfg(stm32f107)] + pub pll3: Option, + #[cfg(stm32f107)] + pub prediv2: PllPreDiv, pub ahb_pre: AHBPrescaler, pub apb1_pre: APBPrescaler, @@ -98,26 +115,39 @@ pub struct Config { #[cfg(all(stm32f3, not(rcc_f37), any(peri_adc3_common, peri_adc34_common)))] pub adc34: AdcClockSource, + #[cfg(stm32f107)] + pub i2s2_src: I2s2src, + #[cfg(stm32f107)] + pub i2s3_src: I2s2src, + /// Per-peripheral kernel clock selection muxes pub mux: super::mux::ClockMux, pub ls: super::LsConfig, } -impl Default for Config { - fn default() -> Self { +impl Config { + pub const fn new() -> Self { Self { hsi: true, hse: None, #[cfg(crs)] - hsi48: Some(Default::default()), + hsi48: Some(crate::rcc::Hsi48Config::new()), sys: Sysclk::HSI, pll: None, + + #[cfg(stm32f107)] + pll2: None, + #[cfg(stm32f107)] + pll3: None, + #[cfg(stm32f107)] + prediv2: PllPreDiv::DIV1, + ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, #[cfg(not(stm32f0))] apb2_pre: APBPrescaler::DIV1, - ls: Default::default(), + ls: crate::rcc::LsConfig::new(), #[cfg(stm32f1)] // ensure ADC is not out of range by default even if APB2 is maxxed out (36mhz) @@ -128,11 +158,22 @@ impl Default for Config { #[cfg(all(stm32f3, not(rcc_f37), any(peri_adc3_common, peri_adc34_common)))] adc34: AdcClockSource::Hclk(AdcHclkPrescaler::Div1), - mux: Default::default(), + #[cfg(stm32f107)] + i2s2_src: I2s2src::SYS, + #[cfg(stm32f107)] + i2s3_src: I2s2src::SYS, + + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Self { + Self::new() + } +} + /// Initialize and Set the clock frequencies pub(crate) unsafe fn init(config: Config) { // Turn on the HSI @@ -157,8 +198,8 @@ pub(crate) unsafe fn init(config: Config) { } Some(hse) => { match hse.mode { - HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), - HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), } RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); @@ -174,6 +215,28 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(not(crs))] let hsi48: Option = None; + // PLL2 and PLL3 + // Configure this before PLL since PLL2 can be the source for PLL. + #[cfg(stm32f107)] + { + // Common prediv for PLL2 and PLL3 + RCC.cfgr2().modify(|w| w.set_prediv2(config.prediv2)); + + // Configure PLL2 + if let Some(pll2) = config.pll2 { + RCC.cfgr2().modify(|w| w.set_pll2mul(pll2.mul)); + RCC.cr().modify(|w| w.set_pll2on(true)); + while !RCC.cr().read().pll2rdy() {} + } + + // Configure PLL3 + if let Some(pll3) = config.pll3 { + RCC.cfgr2().modify(|w| w.set_pll3mul(pll3.mul)); + RCC.cr().modify(|w| w.set_pll3on(true)); + while !RCC.cr().read().pll3rdy() {} + } + } + // Enable PLL let pll = config.pll.map(|pll| { let (src_val, src_freq) = match pll.src { @@ -186,21 +249,44 @@ pub(crate) unsafe fn init(config: Config) { } (Pllsrc::HSI_DIV2, unwrap!(hsi)) } - PllSource::HSE => (Pllsrc::HSE_DIV_PREDIV, unwrap!(hse)), + PllSource::HSE => { + #[cfg(stm32f107)] + RCC.cfgr2().modify(|w| w.set_prediv1src(Prediv1src::HSE)); + + (Pllsrc::HSE_DIV_PREDIV, unwrap!(hse)) + } #[cfg(rcc_f0v4)] PllSource::HSI48 => (Pllsrc::HSI48_DIV_PREDIV, unwrap!(hsi48)), + #[cfg(stm32f107)] + PllSource::PLL2 => { + if config.pll2.is_none() { + panic!("if PLL source is PLL2, Config::pll2 must also be set."); + } + RCC.cfgr2().modify(|w| w.set_prediv1src(Prediv1src::PLL2)); + + let pll2 = unwrap!(config.pll2); + let in_freq = hse.unwrap() / config.prediv2; + let pll2freq = in_freq * pll2.mul; + + (Pllsrc::HSE_DIV_PREDIV, pll2freq) + } }; let in_freq = src_freq / pll.prediv; - assert!(max::PLL_IN.contains(&in_freq)); + + rcc_assert!(max::PLL_IN.contains(&in_freq)); let out_freq = in_freq * pll.mul; - assert!(max::PLL_OUT.contains(&out_freq)); + rcc_assert!(max::PLL_OUT.contains(&out_freq)); #[cfg(not(stm32f1))] RCC.cfgr2().modify(|w| w.set_prediv(pll.prediv)); + + #[cfg(stm32f107)] + RCC.cfgr2().modify(|w| w.set_prediv1(pll.prediv)); + RCC.cfgr().modify(|w| { w.set_pllmul(pll.mul); w.set_pllsrc(src_val); - #[cfg(stm32f1)] + #[cfg(all(stm32f1, not(stm32f107)))] w.set_pllxtpre(pll.prediv); }); RCC.cr().modify(|w| w.set_pllon(true)); @@ -209,10 +295,7 @@ pub(crate) unsafe fn init(config: Config) { out_freq }); - #[cfg(stm32f3)] - let pll_mul_2 = pll.map(|pll| pll * 2u32); - - #[cfg(any(rcc_f1, rcc_f1cl, stm32f3))] + #[cfg(any(rcc_f1, rcc_f1cl, stm32f3, stm32f107))] 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), @@ -228,6 +311,9 @@ pub(crate) unsafe fn init(config: Config) { Sysclk::HSI => unwrap!(hsi), Sysclk::HSE => unwrap!(hse), Sysclk::PLL1_P => unwrap!(pll), + #[cfg(crs)] + Sysclk::HSI48 => unwrap!(hsi48), + #[cfg(not(crs))] _ => unreachable!(), }; @@ -238,15 +324,15 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(stm32f0)] let (pclk2, pclk2_tim) = (pclk1, pclk1_tim); - assert!(max::HCLK.contains(&hclk)); - assert!(max::PCLK1.contains(&pclk1)); + rcc_assert!(max::HCLK.contains(&hclk)); + rcc_assert!(max::PCLK1.contains(&pclk1)); #[cfg(not(stm32f0))] - assert!(max::PCLK2.contains(&pclk2)); + rcc_assert!(max::PCLK2.contains(&pclk2)); #[cfg(stm32f1)] let adc = pclk2 / config.adc_pre; #[cfg(stm32f1)] - assert!(max::ADC.contains(&adc)); + rcc_assert!(max::ADC.contains(&adc)); // Set latency based on HCLK frquency #[cfg(stm32f0)] @@ -289,6 +375,13 @@ pub(crate) unsafe fn init(config: Config) { w.set_adcpre(config.adc_pre); }); + // I2S2 and I2S3 + #[cfg(stm32f107)] + { + RCC.cfgr2().modify(|w| w.set_i2s2src(config.i2s2_src)); + RCC.cfgr2().modify(|w| w.set_i2s3src(config.i2s3_src)); + } + // 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" @@ -328,9 +421,9 @@ pub(crate) unsafe fn init(config: Config) { 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), + AdcHclkPrescaler::Div1 => (1u32, Ckmode::SYNC_DIV1), + AdcHclkPrescaler::Div2 => (2u32, Ckmode::SYNC_DIV2), + AdcHclkPrescaler::Div4 => (4u32, Ckmode::SYNC_DIV4), }; common.ccr().modify(|w| w.set_ckmode(ckmode)); @@ -357,9 +450,9 @@ pub(crate) unsafe fn init(config: Config) { 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), + AdcHclkPrescaler::Div1 => (1u32, Ckmode::SYNC_DIV1), + AdcHclkPrescaler::Div2 => (2u32, Ckmode::SYNC_DIV2), + AdcHclkPrescaler::Div4 => (4u32, Ckmode::SYNC_DIV4), }; common.ccr().modify(|w| w.set_ckmode(ckmode)); @@ -393,9 +486,6 @@ pub(crate) unsafe fn init(config: Config) { 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), diff --git a/embassy-stm32/src/rcc/f247.rs b/embassy-stm32/src/rcc/f247.rs index 61f687d30..8f2e8db5f 100644 --- a/embassy-stm32/src/rcc/f247.rs +++ b/embassy-stm32/src/rcc/f247.rs @@ -1,5 +1,7 @@ use stm32_metapac::flash::vals::Latency; +#[cfg(any(stm32f413, stm32f423, stm32f412))] +pub use crate::pac::rcc::vals::Plli2ssrc as Plli2sSource; pub use crate::pac::rcc::vals::{ 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, @@ -63,6 +65,7 @@ pub struct Pll { /// Used to calculate flash waitstates. See /// RM0033 - Table 3. Number of wait states according to Cortex®-M3 clock frequency #[cfg(stm32f2)] +#[derive(Clone, Copy)] pub enum VoltageScale { /// 2.7 to 3.6 V Range0, @@ -76,12 +79,15 @@ pub enum VoltageScale { /// Configuration of the core clocks #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { pub hsi: bool, pub hse: Option, pub sys: Sysclk, pub pll_src: PllSource, + #[cfg(any(stm32f412, stm32f413, stm32f423))] + pub external_i2s_clock: Option, pub pll: Option, #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] @@ -102,13 +108,15 @@ pub struct Config { pub voltage: VoltageScale, } -impl Default for Config { - fn default() -> Self { +impl Config { + pub const fn new() -> Self { Self { hsi: true, hse: None, sys: Sysclk::HSI, pll_src: PllSource::HSI, + #[cfg(any(stm32f412, stm32f413, stm32f423))] + external_i2s_clock: None, pll: None, #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] plli2s: None, @@ -119,15 +127,21 @@ impl Default for Config { apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, - ls: Default::default(), + ls: crate::rcc::LsConfig::new(), #[cfg(stm32f2)] voltage: VoltageScale::Range3, - mux: Default::default(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Self { + Self::new() + } +} + pub(crate) unsafe fn init(config: Config) { // set VOS to SCALE1, if use PLL // TODO: check real clock speed before set VOS @@ -168,8 +182,8 @@ pub(crate) unsafe fn init(config: Config) { } Some(hse) => { match hse.mode { - HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), - HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), } RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); @@ -183,6 +197,8 @@ pub(crate) unsafe fn init(config: Config) { let pll_input = PllInput { hse, hsi, + #[cfg(any(stm32f412, stm32f413, stm32f423))] + external: config.external_i2s_clock, source: config.pll_src, }; let pll = init_pll(PllInstance::Pll, config.pll, &pll_input); @@ -203,10 +219,10 @@ pub(crate) unsafe fn init(config: Config) { 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::SYSCLK.contains(&sys)); - assert!(max::HCLK.contains(&hclk)); - assert!(max::PCLK1.contains(&pclk1)); - assert!(max::PCLK2.contains(&pclk2)); + rcc_assert!(max::SYSCLK.contains(&sys)); + rcc_assert!(max::HCLK.contains(&hclk)); + rcc_assert!(max::PCLK1.contains(&pclk1)); + rcc_assert!(max::PCLK2.contains(&pclk2)); let rtc = config.ls.init(); @@ -306,7 +322,6 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(dsihost)] dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers - hsi_div488: hsi.map(|hsi| hsi/488u32), hsi_hse: None, afif: None, ); @@ -316,6 +331,8 @@ struct PllInput { source: PllSource, hsi: Option, hse: Option, + #[cfg(any(stm32f412, stm32f413, stm32f423))] + external: Option, } #[derive(Default)] @@ -360,10 +377,17 @@ fn init_pll(instance: PllInstance, config: Option, input: &PllInput) -> Pll let Some(pll) = config else { return PllOutput::default() }; + #[cfg(not(any(stm32f412, stm32f413, stm32f423)))] let pll_src = match input.source { PllSource::HSE => input.hse, PllSource::HSI => input.hsi, }; + #[cfg(any(stm32f412, stm32f413, stm32f423))] + let pll_src = match (input.source, input.external) { + (PllSource::HSE, None) => input.hse, + (PllSource::HSI, None) => input.hsi, + (_, Some(ext)) => Some(ext), + }; let pll_src = pll_src.unwrap(); @@ -412,6 +436,17 @@ fn init_pll(instance: PllInstance, config: Option, input: &PllInput) -> Pll }), #[cfg(any(all(stm32f4, not(stm32f410)), stm32f7))] PllInstance::Plli2s => RCC.plli2scfgr().write(|w| { + #[cfg(any(stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))] + w.set_pllm(pll.prediv); + #[cfg(any(stm32f412, stm32f413, stm32f423))] + { + let plli2ssource = match input.external { + Some(_) => Plli2sSource::EXTERNAL, + None => Plli2sSource::HSE_HSI, + }; + w.set_plli2ssrc(plli2ssource); + } + write_fields!(w); }), #[cfg(stm32f2)] diff --git a/embassy-stm32/src/rcc/g0.rs b/embassy-stm32/src/rcc/g0.rs index c2fa0ca39..ce6398afd 100644 --- a/embassy-stm32/src/rcc/g0.rs +++ b/embassy-stm32/src/rcc/g0.rs @@ -1,10 +1,11 @@ use crate::pac::flash::vals::Latency; pub use crate::pac::pwr::vals::Vos as VoltageRange; pub use crate::pac::rcc::vals::{ - 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, + Hpre as AHBPrescaler, Hsidiv as HsiSysDiv, 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::LSI_FREQ; use crate::time::Hertz; /// HSI speed @@ -28,11 +29,18 @@ pub struct Hse { pub mode: HseMode, } +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hsi { + /// Division factor for HSISYS clock. Default is 1. + pub sys_div: HsiSysDiv, +} + /// 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. +#[derive(Clone, Copy)] pub struct Pll { /// PLL Source clock selection. pub source: PllSource, @@ -55,9 +63,10 @@ pub struct Pll { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { - /// HSI Enable - pub hsi: bool, + /// HSI Configuration + pub hsi: Option, /// HSE Configuration pub hse: Option, @@ -88,26 +97,33 @@ pub struct Config { pub mux: super::mux::ClockMux, } -impl Default for Config { - #[inline] - fn default() -> Config { +impl Config { + pub const fn new() -> Self { Config { - hsi: true, + hsi: Some(Hsi { + sys_div: HsiSysDiv::DIV1, + }), hse: None, sys: Sysclk::HSI, #[cfg(crs)] - hsi48: Some(Default::default()), + hsi48: Some(crate::rcc::Hsi48Config::new()), pll: None, ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, low_power_run: false, - ls: Default::default(), + ls: crate::rcc::LsConfig::new(), voltage_range: VoltageRange::RANGE1, - mux: Default::default(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Config { + Self::new() + } +} + #[derive(Default)] pub struct PllFreq { pub pll_p: Option, @@ -117,7 +133,12 @@ pub struct PllFreq { pub(crate) unsafe fn init(config: Config) { // Turn on the HSI - RCC.cr().modify(|w| w.set_hsion(true)); + RCC.cr().modify(|w| { + w.set_hsion(true); + if let Some(hsi) = config.hsi { + w.set_hsidiv(hsi.sys_div); + } + }); while !RCC.cr().read().hsirdy() {} // Use the HSI clock as system clock during the actual clock setup @@ -125,9 +146,9 @@ pub(crate) unsafe fn init(config: Config) { while RCC.cfgr().read().sws() != Sysclk::HSI {} // Configure HSI - let hsi = match config.hsi { - false => None, - true => Some(HSI_FREQ), + let (hsi, hsisys) = match config.hsi { + None => (None, None), + Some(hsi) => (Some(HSI_FREQ), Some(HSI_FREQ / hsi.sys_div)), }; // Configure HSE @@ -138,8 +159,8 @@ pub(crate) unsafe fn init(config: Config) { } Some(hse) => { match hse.mode { - HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), - HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), } RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); @@ -167,10 +188,9 @@ pub(crate) unsafe fn init(config: Config) { while RCC.cr().read().pllrdy() {} let in_freq = src_freq / pll_config.prediv; - assert!(max::PLL_IN.contains(&in_freq)); + rcc_assert!(max::PLL_IN.contains(&in_freq)); let internal_freq = in_freq * pll_config.mul; - - assert!(max::PLL_VCO.contains(&internal_freq)); + rcc_assert!(max::PLL_VCO.contains(&internal_freq)); RCC.pllcfgr().write(|w| { w.set_plln(pll_config.mul); @@ -184,7 +204,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_pllpen(true); }); let freq = internal_freq / div_p; - assert!(max::PLL_P.contains(&freq)); + rcc_assert!(max::PLL_P.contains(&freq)); freq }); @@ -194,7 +214,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_pllqen(true); }); let freq = internal_freq / div_q; - assert!(max::PLL_Q.contains(&freq)); + rcc_assert!(max::PLL_Q.contains(&freq)); freq }); @@ -204,7 +224,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_pllren(true); }); let freq = internal_freq / div_r; - assert!(max::PLL_R.contains(&freq)); + rcc_assert!(max::PLL_R.contains(&freq)); freq }); @@ -220,21 +240,28 @@ pub(crate) unsafe fn init(config: Config) { }) .unwrap_or_default(); + let rtc = config.ls.init(); + let sys = match config.sys { - Sysclk::HSI => unwrap!(hsi), + Sysclk::HSI => unwrap!(hsisys), Sysclk::HSE => unwrap!(hse), Sysclk::PLL1_R => unwrap!(pll.pll_r), + Sysclk::LSI => { + assert!(config.ls.lsi); + LSI_FREQ + } + Sysclk::LSE => unwrap!(config.ls.lse).frequency, _ => unreachable!(), }; - assert!(max::SYSCLK.contains(&sys)); + rcc_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)); + rcc_assert!(max::HCLK.contains(&hclk)); let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); - assert!(max::PCLK.contains(&pclk1)); + rcc_assert!(max::PCLK.contains(&pclk1)); let latency = match (config.voltage_range, hclk.0) { (VoltageRange::RANGE1, ..=24_000_000) => Latency::WS0, @@ -263,7 +290,7 @@ pub(crate) unsafe fn init(config: Config) { while RCC.cfgr().read().sws() != config.sys {} // Disable HSI if not used - if !config.hsi { + if config.hsi.is_none() { RCC.cr().modify(|w| w.set_hsion(false)); } @@ -272,8 +299,6 @@ pub(crate) unsafe fn init(config: Config) { PWR.cr1().modify(|w| w.set_lpr(true)); } - let rtc = config.ls.init(); - config.mux.init(); set_clocks!( @@ -289,8 +314,6 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(crs)] hsi48: hsi48, rtc: rtc, - hsi_div_8: hsi.map(|h| h / 8u32), - hsi_div_488: hsi.map(|h| h / 488u32), // TODO lsi: None, diff --git a/embassy-stm32/src/rcc/g4.rs b/embassy-stm32/src/rcc/g4.rs index c261c0fed..da13e16aa 100644 --- a/embassy-stm32/src/rcc/g4.rs +++ b/embassy-stm32/src/rcc/g4.rs @@ -32,6 +32,7 @@ pub struct Hse { /// 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. +#[derive(Clone, Copy)] pub struct Pll { /// PLL Source clock selection. pub source: PllSource, @@ -54,6 +55,7 @@ pub struct Pll { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { /// HSI Enable pub hsi: bool, @@ -89,26 +91,31 @@ pub struct Config { pub mux: super::mux::ClockMux, } -impl Default for Config { - #[inline] - fn default() -> Config { +impl Config { + pub const fn new() -> Self { Config { hsi: true, hse: None, sys: Sysclk::HSI, - hsi48: Some(Default::default()), + hsi48: Some(crate::rcc::Hsi48Config::new()), pll: None, ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, low_power_run: false, - ls: Default::default(), + ls: crate::rcc::LsConfig::new(), boost: false, - mux: Default::default(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Config { + Self::new() + } +} + #[derive(Default)] pub struct PllFreq { pub pll_p: Option, @@ -139,8 +146,8 @@ pub(crate) unsafe fn init(config: Config) { } Some(hse) => { match hse.mode { - HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), - HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + HseMode::Bypass => rcc_assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => rcc_assert!(max::HSE_OSC.contains(&hse.freq)), } RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); @@ -167,10 +174,10 @@ pub(crate) unsafe fn init(config: Config) { while RCC.cr().read().pllrdy() {} let in_freq = src_freq / pll_config.prediv; - assert!(max::PLL_IN.contains(&in_freq)); + rcc_assert!(max::PLL_IN.contains(&in_freq)); let internal_freq = in_freq * pll_config.mul; - assert!(max::PLL_VCO.contains(&internal_freq)); + rcc_assert!(max::PLL_VCO.contains(&internal_freq)); RCC.pllcfgr().write(|w| { w.set_plln(pll_config.mul); @@ -184,7 +191,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_pllpen(true); }); let freq = internal_freq / div_p; - assert!(max::PLL_P.contains(&freq)); + rcc_assert!(max::PLL_P.contains(&freq)); freq }); @@ -194,7 +201,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_pllqen(true); }); let freq = internal_freq / div_q; - assert!(max::PLL_Q.contains(&freq)); + rcc_assert!(max::PLL_Q.contains(&freq)); freq }); @@ -204,7 +211,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_pllren(true); }); let freq = internal_freq / div_r; - assert!(max::PLL_R.contains(&freq)); + rcc_assert!(max::PLL_R.contains(&freq)); freq }); @@ -227,16 +234,16 @@ pub(crate) unsafe fn init(config: Config) { _ => unreachable!(), }; - assert!(max::SYSCLK.contains(&sys)); + rcc_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)); + rcc_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)); + rcc_assert!(max::PCLK.contains(&pclk1)); + rcc_assert!(max::PCLK.contains(&pclk2)); // Configure Core Boost mode ([RM0440] p234 – inverted because setting r1mode to 0 enables boost mode!) if config.boost { @@ -318,6 +325,8 @@ pub(crate) unsafe fn init(config: Config) { hse: hse, hsi48: hsi48, rtc: rtc, + lsi: None, + lse: None, ); } diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index e3c7dd158..383f48874 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -34,8 +34,10 @@ pub enum VoltageScale { Scale2, Scale3, } -#[cfg(any(stm32h7rs))] +#[cfg(stm32h7rs)] pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(stm32h7rs, peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::{Usbphycsel, Usbrefcksel}; #[derive(Clone, Copy, Eq, PartialEq)] pub enum HseMode { @@ -111,8 +113,8 @@ pub enum TimerPrescaler { impl From for Timpre { fn from(value: TimerPrescaler) -> Self { match value { - TimerPrescaler::DefaultX2 => Timpre::DEFAULTX2, - TimerPrescaler::DefaultX4 => Timpre::DEFAULTX4, + TimerPrescaler::DefaultX2 => Timpre::DEFAULT_X2, + TimerPrescaler::DefaultX4 => Timpre::DEFAULT_X4, } } } @@ -120,7 +122,7 @@ impl From for Timpre { /// Power supply configuration /// See RM0433 Rev 4 7.4 #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub enum SupplyConfig { /// Default power supply configuration. /// V CORE Power Domains are supplied from the LDO according to VOS. @@ -180,6 +182,7 @@ pub enum SMPSSupplyVoltage { /// Configuration of the core clocks #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { pub hsi: Option, pub hse: Option, @@ -215,13 +218,13 @@ pub struct Config { pub mux: super::mux::ClockMux, } -impl Default for Config { - fn default() -> Self { +impl Config { + pub const fn new() -> Self { Self { hsi: Some(HSIPrescaler::DIV1), hse: None, csi: false, - hsi48: Some(Default::default()), + hsi48: Some(crate::rcc::Hsi48Config::new()), sys: Sysclk::HSI, pll1: None, pll2: None, @@ -245,16 +248,22 @@ impl Default for Config { voltage_scale: VoltageScale::Scale0, #[cfg(rcc_h7rs)] voltage_scale: VoltageScale::HIGH, - ls: Default::default(), + ls: crate::rcc::LsConfig::new(), #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] supply_config: SupplyConfig::LDO, - mux: Default::default(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Self { + Self::new() + } +} + pub(crate) unsafe fn init(config: Config) { #[cfg(any(stm32h7))] let pwr_reg = PWR.cr3(); @@ -556,6 +565,27 @@ pub(crate) unsafe fn init(config: Config) { let rtc = config.ls.init(); + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck = match config.mux.usbphycsel { + Usbphycsel::HSE => hse, + Usbphycsel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Usbphycsel::PLL3_Q => pll3.q, + _ => None, + }; + #[cfg(all(stm32h7rs, peri_usb_otg_hs))] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select USBPHYC reference clock with source frequency of {}, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + #[cfg(stm32h7)] { RCC.d1cfgr().modify(|w| { @@ -592,6 +622,11 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre4(config.apb4_pre); w.set_ppre5(config.apb5_pre); }); + + #[cfg(peri_usb_otg_hs)] + RCC.ahbperckselr().modify(|w| { + w.set_usbrefcksel(usb_refck_sel); + }); } #[cfg(stm32h5)] { @@ -658,7 +693,6 @@ pub(crate) unsafe fn init(config: Config) { hsi: hsi, hsi48: hsi48, csi: csi, - csi_div_122: csi.map(|c| c / 122u32), hse: hse, lse: None, @@ -697,7 +731,9 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(stm32h7rs)] clk48mohci: None, // TODO #[cfg(stm32h7rs)] - usb: None, // TODO + usb: Some(Hertz(48_000_000)), + #[cfg(stm32h5)] + hse_div_rtcpre: None, // TODO ); } @@ -748,7 +784,7 @@ fn init_pll(num: usize, config: Option, input: &PllInput) -> PllOutput { ..=3_999_999 => Pllrge::RANGE2, ..=7_999_999 => Pllrge::RANGE4, ..=16_000_000 => Pllrge::RANGE8, - x => panic!("pll ref_clk out of range: {} mhz", x), + x => panic!("pll ref_clk out of range: {} hz", x), }; // The smaller range (150 to 420 MHz) must @@ -757,18 +793,18 @@ fn init_pll(num: usize, config: Option, input: &PllInput) -> PllOutput { let vco_clk = ref_clk * config.mul; let vco_range = if VCO_RANGE.contains(&vco_clk) { - Pllvcosel::MEDIUMVCO + Pllvcosel::MEDIUM_VCO } else if wide_allowed && VCO_WIDE_RANGE.contains(&vco_clk) { - Pllvcosel::WIDEVCO + Pllvcosel::WIDE_VCO } else { - panic!("pll vco_clk out of range: {} hz", vco_clk.0) + panic!("pll vco_clk out of range: {}", vco_clk) }; let p = config.divp.map(|div| { if num == 0 { // on PLL1, DIVP must be even for most series. // The enum value is 1 less than the divider, so check it's odd. - #[cfg(not(pwr_h7rm0468))] + #[cfg(not(any(pwr_h7rm0468, stm32h7rs)))] assert!(div.to_bits() % 2 == 1); #[cfg(pwr_h7rm0468)] assert!(div.to_bits() % 2 == 1 || div.to_bits() == 0); diff --git a/embassy-stm32/src/rcc/hsi48.rs b/embassy-stm32/src/rcc/hsi48.rs index efabd059f..3ea5c96c9 100644 --- a/embassy-stm32/src/rcc/hsi48.rs +++ b/embassy-stm32/src/rcc/hsi48.rs @@ -18,9 +18,15 @@ pub struct Hsi48Config { pub sync_from_usb: bool, } +impl Hsi48Config { + pub const fn new() -> Self { + Self { sync_from_usb: false } + } +} + impl Default for Hsi48Config { fn default() -> Self { - Self { sync_from_usb: false } + Self::new() } } diff --git a/embassy-stm32/src/rcc/l.rs b/embassy-stm32/src/rcc/l.rs index e9266c65b..81b89046e 100644 --- a/embassy-stm32/src/rcc/l.rs +++ b/embassy-stm32/src/rcc/l.rs @@ -5,6 +5,7 @@ use crate::pac::rcc::regs::Cfgr; 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 Sysclk}; use crate::pac::{FLASH, RCC}; +use crate::rcc::LSI_FREQ; use crate::time::Hertz; /// HSI speed @@ -30,6 +31,7 @@ pub struct Hse { } /// Clocks configuration +#[derive(Clone, Copy)] pub struct Config { // base clock sources pub msi: Option, @@ -66,9 +68,8 @@ pub struct Config { pub mux: super::mux::ClockMux, } -impl Default for Config { - #[inline] - fn default() -> Config { +impl Config { + pub const fn new() -> Self { Config { hse: None, hsi: false, @@ -88,15 +89,21 @@ impl Default for Config { #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] pllsai2: None, #[cfg(crs)] - hsi48: Some(Default::default()), - ls: Default::default(), + hsi48: Some(crate::rcc::Hsi48Config::new()), + ls: crate::rcc::LsConfig::new(), #[cfg(any(stm32l0, stm32l1))] voltage_scale: VoltageScale::RANGE1, - mux: Default::default(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Config { + Self::new() + } +} + #[cfg(stm32wb)] pub const WPAN_DEFAULT: Config = Config { hse: Some(Hse { @@ -181,6 +188,9 @@ pub(crate) unsafe fn init(config: Config) { let rtc = config.ls.init(); + let lse = config.ls.lse.map(|l| l.frequency); + let lsi = config.ls.lsi.then_some(LSI_FREQ); + let msi = config.msi.map(|range| { msi_enable(range); msirange_to_hertz(range) @@ -391,7 +401,7 @@ pub(crate) unsafe fn init(config: Config) { hsi48: hsi48, #[cfg(any(stm32l0, stm32l1))] - pll1_vco_div_2: pll.vco.map(|c| c/2u32), + pll1_vco: pll.vco, #[cfg(not(any(stm32l0, stm32l1)))] pll1_p: pll.p, @@ -424,12 +434,12 @@ pub(crate) unsafe fn init(config: Config) { dsi_phy: None, // DSI PLL clock not supported, don't call `RccPeripheral::frequency()` in the drivers rtc: rtc, + lse: lse, + lsi: lsi, // TODO sai1_extclk: None, sai2_extclk: None, - lsi: None, - lse: None, ); } diff --git a/embassy-stm32/src/rcc/mco.rs b/embassy-stm32/src/rcc/mco.rs index d1ce14c86..c50e071fb 100644 --- a/embassy-stm32/src/rcc/mco.rs +++ b/embassy-stm32/src/rcc/mco.rs @@ -1,6 +1,6 @@ use core::marker::PhantomData; -use embassy_hal_internal::into_ref; +use embassy_hal_internal::PeripheralType; use crate::gpio::{AfType, OutputType, Speed}; #[cfg(not(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37)))] @@ -32,7 +32,7 @@ pub use crate::pac::rcc::vals::Mcosel as McoSource; ))] pub use crate::pac::rcc::vals::{Mco1sel as Mco1Source, Mco2sel as Mco2Source}; use crate::pac::RCC; -use crate::{peripherals, Peripheral}; +use crate::{peripherals, Peri}; #[cfg(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37))] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] @@ -43,7 +43,7 @@ pub enum McoPrescaler { pub(crate) trait SealedMcoInstance {} #[allow(private_bounds)] -pub trait McoInstance: SealedMcoInstance + 'static { +pub trait McoInstance: PeripheralType + SealedMcoInstance + 'static { type Source; #[doc(hidden)] @@ -91,14 +91,7 @@ pub struct Mco<'d, T: McoInstance> { impl<'d, T: McoInstance> Mco<'d, T> { /// Create a new MCO instance. - pub fn new( - _peri: impl Peripheral

+ 'd, - pin: impl Peripheral

> + 'd, - source: T::Source, - prescaler: McoPrescaler, - ) -> Self { - into_ref!(pin); - + pub fn new(_peri: Peri<'d, T>, pin: Peri<'d, impl McoPin>, source: T::Source, prescaler: McoPrescaler) -> Self { critical_section::with(|_| unsafe { T::_apply_clock_settings(source, prescaler); pin.set_as_af(pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 8022a35a4..c41f81816 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -86,7 +86,7 @@ pub(crate) unsafe fn set_freqs(freqs: Clocks) { #[cfg(not(feature = "_dual-core"))] /// Safety: Reads a mutable global. pub(crate) unsafe fn get_freqs() -> &'static Clocks { - CLOCK_FREQS.assume_init_ref() + (*core::ptr::addr_of_mut!(CLOCK_FREQS)).assume_init_ref() } #[cfg(feature = "_dual-core")] @@ -95,8 +95,19 @@ pub(crate) unsafe fn get_freqs() -> &'static Clocks { unwrap!(CLOCK_FREQS_PTR.load(core::sync::atomic::Ordering::SeqCst).as_ref()).assume_init_ref() } +/// Get the current clock configuration of the chip. +pub fn clocks<'a>(_rcc: &'a crate::Peri<'a, crate::peripherals::RCC>) -> &'a Clocks { + // Safety: the existence of a `Peri` means that `rcc::init()` + // has already been called, so `CLOCK_FREQS` must be initialized. + // The clocks could be modified again by `reinit()`, but reinit + // (for this reason) requires an exclusive reference to `Peri`. + unsafe { get_freqs() } +} + pub(crate) trait SealedRccPeripheral { fn frequency() -> Hertz; + #[allow(dead_code)] + fn bus_frequency() -> Hertz; const RCC_INFO: RccInfo; } @@ -171,7 +182,9 @@ impl RccInfo { // Use .get_mut instead of []-operator so that we control how bounds checks happen. // Otherwise, core::fmt will be pulled in here in order to format the integer in the // out-of-bounds error. - if let Some(refcount) = unsafe { crate::_generated::REFCOUNTS.get_mut(refcount_idx) } { + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { *refcount += 1; if *refcount > 1 { return; @@ -235,7 +248,9 @@ impl RccInfo { // Use .get_mut instead of []-operator so that we control how bounds checks happen. // Otherwise, core::fmt will be pulled in here in order to format the integer in the // out-of-bounds error. - if let Some(refcount) = unsafe { crate::_generated::REFCOUNTS.get_mut(refcount_idx) } { + if let Some(refcount) = + unsafe { (*core::ptr::addr_of_mut!(crate::_generated::REFCOUNTS)).get_mut(refcount_idx) } + { *refcount -= 1; if *refcount > 0 { return; @@ -365,3 +380,32 @@ pub fn enable_and_reset() { pub fn disable() { T::RCC_INFO.disable(); } + +/// Re-initialize the `embassy-stm32` clock configuration with the provided configuration. +/// +/// This is useful when you need to alter the CPU clock after configuring peripherals. +/// For instance, configure an external clock via spi or i2c. +/// +/// Please not this only re-configures the rcc and the time driver (not GPIO, EXTI, etc). +/// +/// This should only be called after `init`. +#[cfg(not(feature = "_dual-core"))] +pub fn reinit<'a>(config: Config, _rcc: &'a mut crate::Peri<'a, crate::peripherals::RCC>) { + critical_section::with(|cs| init_rcc(cs, config)) +} + +pub(crate) fn init_rcc(_cs: CriticalSection, config: Config) { + unsafe { + init(config); + + // must be after rcc init + #[cfg(feature = "_time-driver")] + crate::time_driver::init(_cs); + + #[cfg(feature = "low-power")] + { + REFCOUNT_STOP2 = 0; + REFCOUNT_STOP1 = 0; + } + } +} diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index d6331f512..97eb2eb6d 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -1,10 +1,15 @@ pub use crate::pac::pwr::vals::Vos as VoltageScale; +#[cfg(all(peri_usb_otg_hs))] +pub use crate::pac::rcc::vals::Otghssel; 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}; +#[cfg(all(peri_usb_otg_hs))] +pub use crate::pac::{syscfg::vals::Usbrefcksel, SYSCFG}; use crate::pac::{FLASH, PWR, RCC}; +use crate::rcc::LSI_FREQ; use crate::time::Hertz; /// HSI speed @@ -59,9 +64,11 @@ pub struct Pll { pub divr: Option, } +#[derive(Clone, Copy)] pub struct Config { // base clock sources - pub msi: Option, + pub msis: Option, + pub msik: Option, pub hsi: bool, pub hse: Option, pub hsi48: Option, @@ -90,13 +97,14 @@ pub struct Config { pub mux: super::mux::ClockMux, } -impl Default for Config { - fn default() -> Self { +impl Config { + pub const fn new() -> Self { Self { - msi: Some(Msirange::RANGE_4MHZ), + msis: Some(Msirange::RANGE_4MHZ), + msik: Some(Msirange::RANGE_4MHZ), hse: None, hsi: false, - hsi48: Some(Default::default()), + hsi48: Some(crate::rcc::Hsi48Config::new()), pll1: None, pll2: None, pll3: None, @@ -106,18 +114,24 @@ impl Default for Config { apb2_pre: APBPrescaler::DIV1, apb3_pre: APBPrescaler::DIV1, voltage_range: VoltageScale::RANGE1, - ls: Default::default(), - mux: Default::default(), + ls: crate::rcc::LsConfig::new(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Self { + Self::new() + } +} + 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| { + let msis = config.msis.map(|range| { // Check MSI output per RM0456 § 11.4.10 match config.voltage_range { VoltageScale::RANGE4 => { @@ -146,8 +160,36 @@ pub(crate) unsafe fn init(config: Config) { msirange_to_hertz(range) }); + let msik = config.msik.map(|range| { + // Check MSI output per RM0456 § 11.4.10 + match config.voltage_range { + VoltageScale::RANGE4 => { + assert!(msirange_to_hertz(range).0 <= 24_000_000); + } + _ => {} + } + + // RM0456 § 11.8.2: spin until MSIS is off or MSIS is ready before setting its range + loop { + let cr = RCC.cr().read(); + if cr.msikon() == false || cr.msikrdy() == true { + break; + } + } + + RCC.icscr1().modify(|w| { + w.set_msikrange(range); + w.set_msirgsel(Msirgsel::ICSCR1); + }); + RCC.cr().modify(|w| { + w.set_msikon(true); + }); + while !RCC.cr().read().msikrdy() {} + msirange_to_hertz(range) + }); + let hsi = config.hsi.then(|| { - RCC.cr().write(|w| w.set_hsion(true)); + RCC.cr().modify(|w| w.set_hsion(true)); while !RCC.cr().read().hsirdy() {} HSI_FREQ @@ -165,7 +207,7 @@ pub(crate) unsafe fn init(config: Config) { } // Enable HSE, and wait for it to stabilize - RCC.cr().write(|w| { + RCC.cr().modify(|w| { w.set_hseon(true); w.set_hsebyp(hse.mode != HseMode::Oscillator); w.set_hseext(match hse.mode { @@ -180,7 +222,7 @@ pub(crate) unsafe fn init(config: Config) { let hsi48 = config.hsi48.map(super::init_hsi48); - let pll_input = PllInput { hse, hsi, msi }; + let pll_input = PllInput { hse, hsi, msi: msis }; 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); @@ -188,7 +230,7 @@ pub(crate) unsafe fn init(config: Config) { let sys_clk = match config.sys { Sysclk::HSE => hse.unwrap(), Sysclk::HSI => hsi.unwrap(), - Sysclk::MSIS => msi.unwrap(), + Sysclk::MSIS => msis.unwrap(), Sysclk::PLL1_R => pll1.r.unwrap(), }; @@ -263,6 +305,34 @@ pub(crate) unsafe fn init(config: Config) { let rtc = config.ls.init(); + #[cfg(all(stm32u5, peri_usb_otg_hs))] + let usb_refck = match config.mux.otghssel { + Otghssel::HSE => hse, + Otghssel::HSE_DIV_2 => hse.map(|hse_val| hse_val / 2u8), + Otghssel::PLL1_P => pll1.p, + Otghssel::PLL1_P_DIV_2 => pll1.p.map(|pll1p_val| pll1p_val / 2u8), + }; + #[cfg(all(stm32u5, peri_usb_otg_hs))] + let usb_refck_sel = match usb_refck { + Some(clk_val) => match clk_val { + Hertz(16_000_000) => Usbrefcksel::MHZ16, + Hertz(19_200_000) => Usbrefcksel::MHZ19_2, + Hertz(20_000_000) => Usbrefcksel::MHZ20, + Hertz(24_000_000) => Usbrefcksel::MHZ24, + Hertz(26_000_000) => Usbrefcksel::MHZ26, + Hertz(32_000_000) => Usbrefcksel::MHZ32, + _ => panic!("cannot select OTG_HS reference clock with source frequency of {}, must be one of 16, 19.2, 20, 24, 26, 32 MHz", clk_val), + }, + None => Usbrefcksel::MHZ24, + }; + #[cfg(all(stm32u5, peri_usb_otg_hs))] + SYSCFG.otghsphycr().modify(|w| { + w.set_clksel(usb_refck_sel); + }); + + let lse = config.ls.lse.map(|l| l.frequency); + let lsi = config.ls.lsi.then_some(LSI_FREQ); + config.mux.init(); set_clocks!( @@ -275,8 +345,11 @@ pub(crate) unsafe fn init(config: Config) { pclk3: Some(pclk3), pclk1_tim: Some(pclk1_tim), pclk2_tim: Some(pclk2_tim), + msik: msik, hsi48: hsi48, rtc: rtc, + lse: lse, + lsi: lsi, hse: hse, hsi: hsi, pll1_p: pll1.p, @@ -294,12 +367,7 @@ pub(crate) unsafe fn init(config: Config) { // TODO audioclk: None, - hsi48_div_2: None, - lse: None, - lsi: None, - msik: None, shsi: None, - shsi_div_2: None, ); } diff --git a/embassy-stm32/src/rcc/wba.rs b/embassy-stm32/src/rcc/wba.rs index 8e1779d7c..b9fc4e423 100644 --- a/embassy-stm32/src/rcc/wba.rs +++ b/embassy-stm32/src/rcc/wba.rs @@ -15,6 +15,7 @@ pub struct Hse { } /// Clocks configuration +#[derive(Clone, Copy)] pub struct Config { // base clock sources pub hsi: bool, @@ -36,9 +37,8 @@ pub struct Config { pub mux: super::mux::ClockMux, } -impl Default for Config { - #[inline] - fn default() -> Config { +impl Config { + pub const fn new() -> Self { Config { hse: None, hsi: true, @@ -47,13 +47,19 @@ impl Default for Config { apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, apb7_pre: APBPrescaler::DIV1, - ls: Default::default(), + ls: crate::rcc::LsConfig::new(), voltage_scale: VoltageScale::RANGE2, - mux: Default::default(), + mux: super::mux::ClockMux::default(), } } } +impl Default for Config { + fn default() -> Config { + Self::new() + } +} + fn hsi_enable() { RCC.cr().modify(|w| w.set_hsion(true)); while !RCC.cr().read().hsirdy() {} diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index 6f4c81c8a..312f343b9 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -5,12 +5,11 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; -use rand_core::{CryptoRng, RngCore}; use crate::interrupt::typelevel::Interrupt; -use crate::{interrupt, pac, peripherals, rcc, Peripheral}; +use crate::{interrupt, pac, peripherals, rcc, Peri}; static RNG_WAKER: AtomicWaker = AtomicWaker::new(); @@ -43,17 +42,16 @@ impl interrupt::typelevel::Handler for InterruptHandl /// RNG driver. pub struct Rng<'d, T: Instance> { - _inner: PeripheralRef<'d, T>, + _inner: Peri<'d, T>, } impl<'d, T: Instance> Rng<'d, T> { /// Create a new RNG driver. pub fn new( - inner: impl Peripheral

+ 'd, + inner: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, ) -> Self { rcc::enable_and_reset::(); - into_ref!(inner); let mut random = Self { _inner: inner }; random.reset(); @@ -88,10 +86,10 @@ impl<'d, T: Instance> Rng<'d, T> { reg.set_nistc(pac::rng::vals::Nistc::CUSTOM); // set RNG config "A" according to reference manual // this has to be written within the same write access as setting the CONDRST bit - reg.set_rng_config1(pac::rng::vals::RngConfig1::CONFIGA); - reg.set_clkdiv(pac::rng::vals::Clkdiv::NODIV); - reg.set_rng_config2(pac::rng::vals::RngConfig2::CONFIGA_B); - reg.set_rng_config3(pac::rng::vals::RngConfig3::CONFIGA); + reg.set_rng_config1(pac::rng::vals::RngConfig1::CONFIG_A); + reg.set_clkdiv(pac::rng::vals::Clkdiv::NO_DIV); + reg.set_rng_config2(pac::rng::vals::RngConfig2::CONFIG_A_B); + reg.set_rng_config3(pac::rng::vals::RngConfig3::CONFIG_A); reg.set_ced(true); reg.set_ie(false); reg.set_rngen(true); @@ -185,10 +183,9 @@ impl<'d, T: Instance> Rng<'d, T> { Ok(()) } -} -impl<'d, T: Instance> RngCore for Rng<'d, T> { - fn next_u32(&mut self) -> u32 { + /// Get a random u32 + pub fn next_u32(&mut self) -> u32 { loop { let sr = T::regs().sr().read(); if sr.seis() | sr.ceis() { @@ -199,13 +196,15 @@ impl<'d, T: Instance> RngCore for Rng<'d, T> { } } - fn next_u64(&mut self) -> u64 { + /// Get a random u64 + pub fn next_u64(&mut self) -> u64 { let mut rand = self.next_u32() as u64; rand |= (self.next_u32() as u64) << 32; rand } - fn fill_bytes(&mut self, dest: &mut [u8]) { + /// Fill a slice with random bytes + pub fn fill_bytes(&mut self, dest: &mut [u8]) { for chunk in dest.chunks_mut(4) { let rand = self.next_u32(); for (slot, num) in chunk.iter_mut().zip(rand.to_ne_bytes().iter()) { @@ -213,14 +212,53 @@ impl<'d, T: Instance> RngCore for Rng<'d, T> { } } } +} - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { +impl<'d, T: Instance> Drop for Rng<'d, T> { + fn drop(&mut self) { + T::regs().cr().modify(|reg| { + reg.set_rngen(false); + }); + rcc::disable::(); + } +} + +impl<'d, T: Instance> rand_core_06::RngCore for Rng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { self.fill_bytes(dest); Ok(()) } } -impl<'d, T: Instance> CryptoRng for Rng<'d, T> {} +impl<'d, T: Instance> rand_core_06::CryptoRng for Rng<'d, T> {} + +impl<'d, T: Instance> rand_core_09::RngCore for Rng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill_bytes(dest); + } +} + +impl<'d, T: Instance> rand_core_09::CryptoRng for Rng<'d, T> {} trait SealedInstance { fn regs() -> pac::rng::Rng; @@ -228,7 +266,7 @@ trait SealedInstance { /// RNG instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + Peripheral

+ crate::rcc::RccPeripheral + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + crate::rcc::RccPeripheral + 'static + Send { /// Interrupt for this RNG instance. type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-stm32/src/rtc/datetime.rs b/embassy-stm32/src/rtc/datetime.rs index 32732e96e..8b420bb6e 100644 --- a/embassy-stm32/src/rtc/datetime.rs +++ b/embassy-stm32/src/rtc/datetime.rs @@ -3,6 +3,7 @@ use chrono::{Datelike, NaiveDate, Timelike, Weekday}; /// Errors regarding the [`DateTime`] struct. #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. InvalidYear, @@ -21,9 +22,12 @@ pub enum Error { InvalidMinute, /// The [DateTime] contains an invalid second value. Must be between `0..=59`. InvalidSecond, + /// The [DateTime] contains an invalid microsecond value. Must be between `0..=999_999`. + InvalidMicrosecond, } /// Structure containing date and time information +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct DateTime { /// 0..4095 year: u16, @@ -39,6 +43,8 @@ pub struct DateTime { minute: u8, /// 0..59 second: u8, + /// 0..999_999 + usecond: u32, } impl DateTime { @@ -77,6 +83,11 @@ impl DateTime { self.second } + /// Get the microsecond (0..=999_999) + pub const fn microsecond(&self) -> u32 { + self.usecond + } + /// Create a new DateTime with the given information. pub fn from( year: u16, @@ -86,6 +97,7 @@ impl DateTime { hour: u8, minute: u8, second: u8, + usecond: u32, ) -> Result { if year > 4095 { Err(Error::InvalidYear) @@ -99,6 +111,8 @@ impl DateTime { Err(Error::InvalidMinute) } else if second > 59 { Err(Error::InvalidSecond) + } else if usecond > 999_999 { + Err(Error::InvalidMicrosecond) } else { Ok(Self { year, @@ -108,6 +122,7 @@ impl DateTime { hour, minute, second, + usecond, }) } } @@ -124,6 +139,7 @@ impl From for DateTime { hour: date_time.hour() as u8, minute: date_time.minute() as u8, second: date_time.second() as u8, + usecond: date_time.and_utc().timestamp_subsec_micros(), } } } @@ -133,7 +149,12 @@ impl From for chrono::NaiveDateTime { fn from(date_time: DateTime) -> Self { NaiveDate::from_ymd_opt(date_time.year as i32, date_time.month as u32, date_time.day as u32) .unwrap() - .and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32) + .and_hms_micro_opt( + date_time.hour as u32, + date_time.minute as u32, + date_time.second as u32, + date_time.usecond, + ) .unwrap() } } @@ -141,6 +162,7 @@ impl From for chrono::NaiveDateTime { /// A day of the week #[repr(u8)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[allow(missing_docs)] pub enum DayOfWeek { Monday = 1, diff --git a/embassy-stm32/src/rtc/low_power.rs b/embassy-stm32/src/rtc/low_power.rs index b9aaa63b9..cd075f3de 100644 --- a/embassy-stm32/src/rtc/low_power.rs +++ b/embassy-stm32/src/rtc/low_power.rs @@ -65,7 +65,9 @@ pub(crate) enum WakeupPrescaler { Div16 = 16, } -#[cfg(any(stm32f4, stm32l0, stm32g4, stm32l5, stm32wb, stm32h5, stm32g0))] +#[cfg(any( + stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0 +))] impl From for crate::pac::rtc::vals::Wucksel { fn from(val: WakeupPrescaler) -> Self { use crate::pac::rtc::vals::Wucksel; @@ -79,7 +81,9 @@ impl From for crate::pac::rtc::vals::Wucksel { } } -#[cfg(any(stm32f4, stm32l0, stm32g4, stm32l5, stm32wb, stm32h5, stm32g0))] +#[cfg(any( + stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0, stm32u5, stm32u0 +))] impl From for WakeupPrescaler { fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { use crate::pac::rtc::vals::Wucksel; @@ -219,12 +223,29 @@ impl Rtc { pub(crate) fn enable_wakeup_line(&self) { use crate::interrupt::typelevel::Interrupt; - use crate::pac::EXTI; ::WakeupInterrupt::unpend(); unsafe { ::WakeupInterrupt::enable() }; - EXTI.rtsr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); - EXTI.imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); + #[cfg(not(any(stm32u5, stm32u0)))] + { + use crate::pac::EXTI; + EXTI.rtsr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); + + #[cfg(not(stm32wb))] + { + EXTI.imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); + } + #[cfg(stm32wb)] + { + EXTI.cpu(0).imr(0).modify(|w| w.set_line(RTC::EXTI_WAKEUP_LINE, true)); + } + } + #[cfg(stm32u5)] + { + use crate::pac::RCC; + RCC.srdamr().modify(|w| w.set_rtcapbamen(true)); + RCC.apb3smenr().modify(|w| w.set_rtcapbsmen(true)); + } } } diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs index fe57cfe66..49f423f37 100644 --- a/embassy-stm32/src/rtc/mod.rs +++ b/embassy-stm32/src/rtc/mod.rs @@ -25,17 +25,18 @@ use crate::time::Hertz; ), path = "v2.rs" )] -#[cfg_attr(any(rtc_v3, rtc_v3u5, rtc_v3l5), path = "v3.rs")] +#[cfg_attr(any(rtc_v3, rtc_v3u5, rtc_v3l5, rtc_v3h7rs), path = "v3.rs")] mod _version; #[allow(unused_imports)] pub use _version::*; -use embassy_hal_internal::Peripheral; use crate::peripherals::RTC; +use crate::Peri; /// Errors that can occur on methods on [RtcClock] #[non_exhaustive] #[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RtcError { /// An invalid DateTime was given or stored on the hardware. InvalidDateTime(DateTimeError), @@ -59,7 +60,7 @@ impl RtcTimeProvider { /// /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. pub fn now(&self) -> Result { - self.read(|dr, tr, _| { + self.read(|dr, tr, _ss| { let second = bcd2_to_byte((tr.st(), tr.su())); let minute = bcd2_to_byte((tr.mnt(), tr.mnu())); let hour = bcd2_to_byte((tr.ht(), tr.hu())); @@ -69,7 +70,17 @@ impl RtcTimeProvider { let month = bcd2_to_byte((dr.mt() as u8, dr.mu())); let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 2000_u16; - DateTime::from(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime) + // Calculate second fraction and multiply to microseconds + // Formula from RM0410 + #[cfg(not(rtc_v2f2))] + let us = { + let prediv = RTC::regs().prer().read().prediv_s() as f32; + (((prediv - _ss as f32) / (prediv + 1.0)) * 1e6).min(999_999.0) as u32 + }; + #[cfg(rtc_v2f2)] + let us = 0; + + DateTime::from(year, month, day, weekday, hour, minute, second, us).map_err(RtcError::InvalidDateTime) }) } @@ -140,7 +151,7 @@ pub enum RtcCalibrationCyclePeriod { impl Rtc { /// Create a new RTC instance. - pub fn new(_rtc: impl Peripheral

, rtc_config: RtcConfig) -> Self { + pub fn new(_rtc: Peri<'static, RTC>, rtc_config: RtcConfig) -> Self { #[cfg(not(any(stm32l0, stm32f3, stm32l1, stm32f0, stm32f2)))] crate::rcc::enable_and_reset::(); @@ -285,6 +296,7 @@ trait SealedInstance { const BACKUP_REGISTER_COUNT: usize; #[cfg(feature = "low-power")] + #[cfg(not(any(stm32u5, stm32u0)))] const EXTI_WAKEUP_LINE: usize; #[cfg(feature = "low-power")] diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index cdc1cb299..28380a3c0 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs @@ -77,7 +77,7 @@ impl super::Rtc { // When the offset is positive (0 to 512), the opposite of // the offset (512 - offset) is masked, i.e. for the // maximum offset (512), 0 pulses are masked. - w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASEFREQ); + w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASE_FREQ); w.set_calm(512 - clock_drift as u16); } else { // Minimum (about -510.7) rounds to -511. @@ -86,7 +86,7 @@ impl super::Rtc { // When the offset is negative or zero (-511 to 0), // the absolute offset is masked, i.e. for the minimum // offset (-511), 511 pulses are masked. - w.set_calp(stm32_metapac::rtc::vals::Calp::NOCHANGE); + w.set_calp(stm32_metapac::rtc::vals::Calp::NO_CHANGE); w.set_calm((clock_drift * -1.0) as u16); } }); @@ -131,10 +131,16 @@ impl SealedInstance for crate::peripherals::RTC { #[cfg(all(feature = "low-power", stm32f4))] const EXTI_WAKEUP_LINE: usize = 22; + #[cfg(all(feature = "low-power", stm32l4))] + const EXTI_WAKEUP_LINE: usize = 20; + #[cfg(all(feature = "low-power", stm32l0))] const EXTI_WAKEUP_LINE: usize = 20; - #[cfg(all(feature = "low-power", stm32f4))] + #[cfg(all(feature = "low-power", stm32wb))] + const EXTI_WAKEUP_LINE: usize = 19; + + #[cfg(all(feature = "low-power", any(stm32f4, stm32l4, stm32wb)))] type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; #[cfg(all(feature = "low-power", stm32l0))] diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs index 02fd5272e..39aa6c5cb 100644 --- a/embassy-stm32/src/rtc/v3.rs +++ b/embassy-stm32/src/rtc/v3.rs @@ -12,7 +12,7 @@ impl super::Rtc { self.write(true, |rtc| { rtc.cr().modify(|w| { w.set_bypshad(true); - w.set_fmt(Fmt::TWENTYFOURHOUR); + w.set_fmt(Fmt::TWENTY_FOUR_HOUR); w.set_osel(Osel::DISABLED); w.set_pol(Pol::HIGH); }); @@ -25,7 +25,7 @@ impl super::Rtc { // TODO: configuration for output pins rtc.cr().modify(|w| { w.set_out2en(false); - w.set_tampalrm_type(TampalrmType::PUSHPULL); + w.set_tampalrm_type(TampalrmType::PUSH_PULL); w.set_tampalrm_pu(false); }); }); @@ -56,10 +56,10 @@ impl super::Rtc { rtc.calr().write(|w| { match period { RtcCalibrationCyclePeriod::Seconds8 => { - w.set_calw8(Calw8::EIGHTSECONDS); + w.set_calw8(Calw8::EIGHT_SECONDS); } RtcCalibrationCyclePeriod::Seconds16 => { - w.set_calw16(Calw16::SIXTEENSECONDS); + w.set_calw16(Calw16::SIXTEEN_SECONDS); } RtcCalibrationCyclePeriod::Seconds32 => { // Set neither `calw8` nor `calw16` to use 32 seconds @@ -79,7 +79,7 @@ impl super::Rtc { // When the offset is positive (0 to 512), the opposite of // the offset (512 - offset) is masked, i.e. for the // maximum offset (512), 0 pulses are masked. - w.set_calp(Calp::INCREASEFREQ); + w.set_calp(Calp::INCREASE_FREQ); w.set_calm(512 - clock_drift as u16); } else { // Minimum (about -510.7) rounds to -511. @@ -88,7 +88,7 @@ impl super::Rtc { // When the offset is negative or zero (-511 to 0), // the absolute offset is masked, i.e. for the minimum // offset (-511), 511 pulses are masked. - w.set_calp(Calp::NOCHANGE); + w.set_calp(Calp::NO_CHANGE); w.set_calm((clock_drift * -1.0) as u16); } }); @@ -133,12 +133,20 @@ impl SealedInstance for crate::peripherals::RTC { cfg_if::cfg_if!( if #[cfg(stm32g4)] { const EXTI_WAKEUP_LINE: usize = 20; - type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; } else if #[cfg(stm32g0)] { const EXTI_WAKEUP_LINE: usize = 19; - type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; } else if #[cfg(any(stm32l5, stm32h5))] { const EXTI_WAKEUP_LINE: usize = 17; + } + ); + + #[cfg(feature = "low-power")] + cfg_if::cfg_if!( + if #[cfg(stm32g4)] { + type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; + } else if #[cfg(any(stm32g0, stm32u0))] { + type WakeupInterrupt = crate::interrupt::typelevel::RTC_TAMP; + } else if #[cfg(any(stm32l5, stm32h5, stm32u5))] { type WakeupInterrupt = crate::interrupt::typelevel::RTC; } ); diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index c48d81b5f..0c9c27797 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -4,7 +4,7 @@ use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::PeripheralType; pub use crate::dma::word; #[cfg(not(gpdma))] @@ -12,7 +12,7 @@ use crate::dma::{ringbuffer, Channel, ReadableRingBuffer, Request, TransferOptio use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::pac::sai::{vals, Sai as Regs}; use crate::rcc::{self, RccPeripheral}; -use crate::{peripherals, Peripheral}; +use crate::{peripherals, Peri}; /// SAI error #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -27,8 +27,14 @@ pub enum Error { } #[cfg(not(gpdma))] -impl From for Error { - fn from(_: ringbuffer::OverrunError) -> Self { +impl From for Error { + fn from(#[allow(unused)] err: ringbuffer::Error) -> Self { + #[cfg(feature = "defmt")] + { + if err == ringbuffer::Error::DmaUnsynced { + defmt::error!("Ringbuffer broken invariants detected!"); + } + } Self::Overrun } } @@ -46,12 +52,12 @@ impl Mode { const fn mode(&self, tx_rx: TxRx) -> vals::Mode { match tx_rx { TxRx::Transmitter => match self { - Mode::Master => vals::Mode::MASTERTX, - Mode::Slave => vals::Mode::SLAVETX, + Mode::Master => vals::Mode::MASTER_TX, + Mode::Slave => vals::Mode::SLAVE_TX, }, TxRx::Receiver => match self { - Mode::Master => vals::Mode::MASTERRX, - Mode::Slave => vals::Mode::SLAVERX, + Mode::Master => vals::Mode::MASTER_RX, + Mode::Slave => vals::Mode::SLAVE_RX, }, } } @@ -80,7 +86,7 @@ impl SlotSize { #[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, + SlotSize::DataSize => vals::Slotsz::DATA_SIZE, SlotSize::Channel16 => vals::Slotsz::BIT16, SlotSize::Channel32 => vals::Slotsz::BIT32, } @@ -149,8 +155,8 @@ impl MuteValue { #[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, - MuteValue::LastValue => vals::Muteval::SENDLAST, + MuteValue::Zero => vals::Muteval::SEND_ZERO, + MuteValue::LastValue => vals::Muteval::SEND_LAST, } } } @@ -184,7 +190,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(any(sai_v4_2pdm, sai_v4_4pdm))] + #[cfg(any(sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] External(SyncInputInstance), } @@ -193,14 +199,14 @@ impl SyncInput { match self { SyncInput::None => vals::Syncen::ASYNCHRONOUS, SyncInput::Internal => vals::Syncen::INTERNAL, - #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] + #[cfg(any(sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] SyncInput::External(_) => vals::Syncen::EXTERNAL, } } } /// SAI instance to sync from. -#[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] +#[cfg(any(sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] #[derive(Copy, Clone, PartialEq)] #[allow(missing_docs)] pub enum SyncInputInstance { @@ -245,8 +251,8 @@ impl BitOrder { #[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, - BitOrder::MsbFirst => vals::Lsbfirst::MSBFIRST, + BitOrder::LsbFirst => vals::Lsbfirst::LSB_FIRST, + BitOrder::MsbFirst => vals::Lsbfirst::MSB_FIRST, } } } @@ -264,8 +270,8 @@ impl FrameSyncOffset { #[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, - FrameSyncOffset::BeforeFirstBit => vals::Fsoff::BEFOREFIRST, + FrameSyncOffset::OnFirstBit => vals::Fsoff::ON_FIRST, + FrameSyncOffset::BeforeFirstBit => vals::Fsoff::BEFORE_FIRST, } } } @@ -283,8 +289,8 @@ impl FrameSyncPolarity { #[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, - FrameSyncPolarity::ActiveHigh => vals::Fspol::RISINGEDGE, + FrameSyncPolarity::ActiveLow => vals::Fspol::FALLING_EDGE, + FrameSyncPolarity::ActiveHigh => vals::Fspol::RISING_EDGE, } } } @@ -319,8 +325,8 @@ impl ClockStrobe { #[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, - ClockStrobe::Rising => vals::Ckstr::RISINGEDGE, + ClockStrobe::Falling => vals::Ckstr::FALLING_EDGE, + ClockStrobe::Rising => vals::Ckstr::RISING_EDGE, } } } @@ -337,8 +343,8 @@ impl ComplementFormat { #[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, - ComplementFormat::TwosComplement => vals::Cpl::TWOSCOMPLEMENT, + ComplementFormat::OnesComplement => vals::Cpl::ONES_COMPLEMENT, + ComplementFormat::TwosComplement => vals::Cpl::TWOS_COMPLEMENT, } } } @@ -356,8 +362,8 @@ impl Companding { #[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, - Companding::MuLaw => vals::Comp::MULAW, + Companding::None => vals::Comp::NO_COMPANDING, + Companding::MuLaw => vals::Comp::MU_LAW, Companding::ALaw => vals::Comp::ALAW, } } @@ -375,7 +381,7 @@ impl OutputDrive { #[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, + OutputDrive::OnStart => vals::Outdriv::ON_START, OutputDrive::Immediately => vals::Outdriv::IMMEDIATELY, } } @@ -661,19 +667,19 @@ fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AfType, AfType) { //sd is defined by tx/rx mode match tx_rx { TxRx::Transmitter => AfType::output(OutputType::PushPull, Speed::VeryHigh), - TxRx::Receiver => AfType::input(Pull::None), + TxRx::Receiver => AfType::input(Pull::Down), // Ensure mute level when no input is connected. }, //clocks (mclk, sck and fs) are defined by master/slave match mode { Mode::Master => AfType::output(OutputType::PushPull, Speed::VeryHigh), - Mode::Slave => AfType::input(Pull::None), + Mode::Slave => AfType::input(Pull::Down), // Ensure no clocks when no input is connected. }, ) } #[cfg(not(gpdma))] fn get_ring_buffer<'d, T: Instance, W: word::Word>( - dma: impl Peripheral

+ 'd, + dma: Peri<'d, impl Channel>, dma_buf: &'d mut [W], request: Request, sub_block: WhichSubBlock, @@ -698,12 +704,12 @@ fn update_synchronous_config(config: &mut Config) { config.mode = Mode::Slave; config.sync_output = false; - #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm))] + #[cfg(any(sai_v1, sai_v2))] { config.sync_input = SyncInput::Internal; } - #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] + #[cfg(any(sai_v3_2pdm, sai_v3_4pdm, 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 @@ -712,16 +718,15 @@ fn update_synchronous_config(config: &mut Config) { } /// SAI subblock instance. -pub struct SubBlock<'d, T, S: SubBlockInstance> { - peri: PeripheralRef<'d, T>, +pub struct SubBlock<'d, T: Instance, S: SubBlockInstance> { + peri: Peri<'d, T>, _phantom: PhantomData, } /// Split the main SAIx peripheral into the two subblocks. /// /// You can then create a [`Sai`] driver for each each half. -pub fn split_subblocks<'d, T: Instance>(peri: impl Peripheral

+ 'd) -> (SubBlock<'d, T, A>, SubBlock<'d, T, B>) { - into_ref!(peri); +pub fn split_subblocks<'d, T: Instance>(peri: Peri<'d, T>) -> (SubBlock<'d, T, A>, SubBlock<'d, T, B>) { rcc::enable_and_reset::(); ( @@ -738,11 +743,11 @@ pub fn split_subblocks<'d, T: Instance>(peri: impl Peripheral

+ 'd) -> (S /// SAI sub-block driver. pub struct Sai<'d, T: Instance, W: word::Word> { - _peri: PeripheralRef<'d, T>, - sd: Option>, - fs: Option>, - sck: Option>, - mclk: Option>, + _peri: Peri<'d, T>, + sd: Option>, + fs: Option>, + sck: Option>, + mclk: Option>, #[cfg(gpdma)] ring_buffer: PhantomData, #[cfg(not(gpdma))] @@ -757,16 +762,14 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { /// You can obtain the [`SubBlock`] with [`split_subblocks`]. pub fn new_asynchronous_with_mclk( peri: SubBlock<'d, T, S>, - sck: impl Peripheral

> + 'd, - sd: impl Peripheral

> + 'd, - fs: impl Peripheral

> + 'd, - mclk: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + sck: Peri<'d, impl SckPin>, + sd: Peri<'d, impl SdPin>, + fs: Peri<'d, impl FsPin>, + mclk: Peri<'d, impl MclkPin>, + dma: Peri<'d, impl Channel + Dma>, dma_buf: &'d mut [W], mut config: Config, ) -> Self { - into_ref!(mclk); - let (_sd_af_type, ck_af_type) = get_af_types(config.mode, config.tx_rx); mclk.set_as_af(mclk.af_num(), ck_af_type); @@ -782,15 +785,14 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { /// You can obtain the [`SubBlock`] with [`split_subblocks`]. pub fn new_asynchronous( peri: SubBlock<'d, T, S>, - sck: impl Peripheral

> + 'd, - sd: impl Peripheral

> + 'd, - fs: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + sck: Peri<'d, impl SckPin>, + sd: Peri<'d, impl SdPin>, + fs: Peri<'d, impl FsPin>, + dma: Peri<'d, impl Channel + Dma>, dma_buf: &'d mut [W], config: Config, ) -> Self { let peri = peri.peri; - into_ref!(peri, dma, sck, sd, fs); let (sd_af_type, ck_af_type) = get_af_types(config.mode, config.tx_rx); sd.set_as_af(sd.af_num(), sd_af_type); @@ -803,10 +805,10 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { Self::new_inner( peri, sub_block, - Some(sck.map_into()), + Some(sck.into()), None, - Some(sd.map_into()), - Some(fs.map_into()), + Some(sd.into()), + Some(fs.into()), get_ring_buffer::(dma, dma_buf, request, sub_block, config.tx_rx), config, ) @@ -817,15 +819,14 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { /// You can obtain the [`SubBlock`] with [`split_subblocks`]. pub fn new_synchronous( peri: SubBlock<'d, T, S>, - sd: impl Peripheral

> + 'd, - dma: impl Peripheral

> + 'd, + sd: Peri<'d, impl SdPin>, + dma: Peri<'d, impl Channel + Dma>, dma_buf: &'d mut [W], mut config: Config, ) -> Self { update_synchronous_config(&mut config); let peri = peri.peri; - into_ref!(dma, peri, sd); let (sd_af_type, _ck_af_type) = get_af_types(config.mode, config.tx_rx); sd.set_as_af(sd.af_num(), sd_af_type); @@ -838,7 +839,7 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { sub_block, None, None, - Some(sd.map_into()), + Some(sd.into()), None, get_ring_buffer::(dma, dma_buf, request, sub_block, config.tx_rx), config, @@ -846,22 +847,25 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { } fn new_inner( - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, sub_block: WhichSubBlock, - sck: Option>, - mclk: Option>, - sd: Option>, - fs: Option>, + sck: Option>, + mclk: Option>, + sd: Option>, + fs: Option>, ring_buffer: RingBuffer<'d, W>, config: Config, ) -> Self { + let ch = T::REGS.ch(sub_block as usize); + #[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_2pdm, sai_v4_4pdm))] + ch.cr2().modify(|w| w.set_fflush(true)); + + #[cfg(any(sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] { if let SyncInput::External(i) = config.sync_input { T::REGS.gcr().modify(|w| { @@ -882,7 +886,6 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { #[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_mode(config.mode.mode(if Self::is_transmitter(&ring_buffer) { TxRx::Transmitter @@ -899,9 +902,9 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { w.set_mckdiv(config.master_clock_divider.mckdiv()); w.set_nodiv( if config.master_clock_divider == MasterClockDivider::MasterClockDisabled { - vals::Nodiv::NODIV + vals::Nodiv::NO_DIV } else { - vals::Nodiv::MASTERCLOCK + vals::Nodiv::MASTER_CLOCK }, ); w.set_dmaen(true); @@ -928,7 +931,7 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { w.set_nbslot(config.slot_count.0 as u8 - 1); w.set_slotsz(config.slot_size.slotsz()); w.set_fboff(config.first_bit_offset.0 as u8); - w.set_sloten(vals::Sloten(config.slot_enable as u16)); + w.set_sloten(vals::Sloten::from_bits(config.slot_enable as u16)); }); ch.cr1().modify(|w| w.set_saien(true)); @@ -939,7 +942,7 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { } Self { - _peri: peri.into_ref(), + _peri: peri, sub_block, sck, mclk, @@ -950,13 +953,14 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { } /// Start the SAI driver. - pub fn start(&mut self) { + /// + /// Only receivers can be started. Transmitters are started on the first writing operation. + pub fn start(&mut self) -> Result<(), Error> { match self.ring_buffer { - RingBuffer::Writable(ref mut rb) => { - rb.start(); - } + RingBuffer::Writable(_) => Err(Error::NotAReceiver), RingBuffer::Readable(ref mut rb) => { rb.start(); + Ok(()) } } } @@ -973,22 +977,48 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { rcc::enable_and_reset::(); } - /// Flush. - pub fn flush(&mut self) { - let ch = T::REGS.ch(self.sub_block as usize); - ch.cr1().modify(|w| w.set_saien(false)); - ch.cr2().modify(|w| w.set_fflush(true)); - ch.cr1().modify(|w| w.set_saien(true)); - } - /// Enable or disable mute. pub fn set_mute(&mut self, value: bool) { let ch = T::REGS.ch(self.sub_block as usize); ch.cr2().modify(|w| w.set_mute(value)); } + /// Determine the mute state of the receiver. + /// + /// Clears the mute state flag in the status register. + pub fn is_muted(&self) -> Result { + match &self.ring_buffer { + RingBuffer::Readable(_) => { + let ch = T::REGS.ch(self.sub_block as usize); + let mute_state = ch.sr().read().mutedet(); + ch.clrfr().write(|w| w.set_cmutedet(true)); + Ok(mute_state) + } + _ => Err(Error::NotAReceiver), + } + } + + /// Wait until any SAI write error occurs. + /// + /// One useful application for this is stopping playback as soon as the SAI + /// experiences an overrun of the ring buffer. Then, instead of letting + /// the SAI peripheral play the last written buffer over and over again, SAI + /// can be muted or dropped instead. + pub async fn wait_write_error(&mut self) -> Result<(), Error> { + match &mut self.ring_buffer { + RingBuffer::Writable(buffer) => { + buffer.wait_write_error().await?; + Ok(()) + } + _ => return Err(Error::NotATransmitter), + } + } + /// Write data to the SAI ringbuffer. /// + /// The first write starts the DMA after filling the ring buffer with the provided data. + /// This ensures that the DMA does not run before data is available in the ring buffer. + /// /// This appends the data to the buffer and returns immediately. The /// data will be transmitted in the background. /// @@ -996,7 +1026,12 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { pub async fn write(&mut self, data: &[W]) -> Result<(), Error> { match &mut self.ring_buffer { RingBuffer::Writable(buffer) => { - buffer.write_exact(data).await?; + if buffer.is_running() { + buffer.write_exact(data).await?; + } else { + buffer.write_immediate(data)?; + buffer.start(); + } Ok(()) } _ => return Err(Error::NotATransmitter), @@ -1024,6 +1059,7 @@ 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)); + ch.cr2().modify(|w| w.set_fflush(true)); self.fs.as_ref().map(|x| x.set_as_disconnected()); self.sd.as_ref().map(|x| x.set_as_disconnected()); self.sck.as_ref().map(|x| x.set_as_disconnected()); @@ -1065,7 +1101,7 @@ impl SubBlockInstance for B {} /// SAI instance trait. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral {} pin_trait!(SckPin, Instance, SubBlockInstance); pin_trait!(FsPin, Instance, SubBlockInstance); diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 44ff9fcd5..6a02aae70 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -8,11 +8,15 @@ use core::ops::{Deref, DerefMut}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_hal_internal::{Peri, PeripheralType}; use embassy_sync::waitqueue::AtomicWaker; -use sdio_host::{BusWidth, CardCapacity, CardStatus, CurrentState, SDStatus, CID, CSD, OCR, SCR}; +use sdio_host::common_cmd::{self, Resp, ResponseLen}; +use sdio_host::emmc::{ExtCSD, EMMC}; +use sdio_host::sd::{BusWidth, CardCapacity, CardStatus, CurrentState, SDStatus, CIC, CID, CSD, OCR, RCA, SCR, SD}; +use sdio_host::{emmc_cmd, sd_cmd, Cmd}; -use crate::dma::NoDma; +#[cfg(sdmmc_v1)] +use crate::dma::ChannelAndRequest; #[cfg(gpio_v2)] use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, SealedPin, Speed}; @@ -20,7 +24,7 @@ use crate::interrupt::typelevel::Interrupt; use crate::pac::sdmmc::Sdmmc as RegBlock; use crate::rcc::{self, RccPeripheral}; use crate::time::Hertz; -use crate::{interrupt, peripherals, Peripheral}; +use crate::{interrupt, peripherals}; /// Interrupt handler. pub struct InterruptHandler { @@ -135,51 +139,59 @@ pub enum Error { UnsupportedCardVersion, /// Unsupported card type. UnsupportedCardType, + /// Unsupported voltage. + UnsupportedVoltage, /// CRC error. Crc, /// No card inserted. NoCard, + /// 8-lane buses are not supported for SD cards. + BusWidth, /// Bad clock supplied to the SDMMC peripheral. BadClock, /// Signaling switch failed. SignalingSwitchFailed, + /// Underrun error + Underrun, /// ST bit error. #[cfg(sdmmc_v1)] StBitErr, } -/// A SD command -struct Cmd { - cmd: u8, - arg: u32, - resp: Response, -} - #[derive(Clone, Copy, Debug, Default)] /// SD Card pub struct Card { /// The type of this card pub card_type: CardCapacity, /// Operation Conditions Register - pub ocr: OCR, + pub ocr: OCR, /// Relative Card Address - pub rca: u32, + pub rca: u16, /// Card ID - pub cid: CID, + pub cid: CID, /// Card Specific Data - pub csd: CSD, + pub csd: CSD, /// SD CARD Configuration Register pub scr: SCR, /// SD Status pub status: SDStatus, } -impl Card { - /// Size in bytes - pub fn size(&self) -> u64 { - // SDHC / SDXC / SDUC - u64::from(self.csd.block_count()) * 512 - } +#[derive(Clone, Copy, Debug, Default)] +/// eMMC storage +pub struct Emmc { + /// The capacity of this card + pub capacity: CardCapacity, + /// Operation Conditions Register + pub ocr: OCR, + /// Relative Card Address + pub rca: u16, + /// Card ID + pub cid: CID, + /// Card Specific Data + pub csd: CSD, + /// Extended Card Specific Data + pub ext_csd: ExtCSD, } #[repr(u8)] @@ -188,22 +200,12 @@ enum PowerCtrl { On = 0b11, } -#[repr(u32)] -#[allow(dead_code)] -#[allow(non_camel_case_types)] -enum CmdAppOper { - VOLTAGE_WINDOW_SD = 0x8010_0000, - HIGH_CAPACITY = 0x4000_0000, - SDMMC_STD_CAPACITY = 0x0000_0000, - SDMMC_CHECK_PATTERN = 0x0000_01AA, - SD_SWITCH_1_8V_CAPACITY = 0x0100_0000, -} - -#[derive(Eq, PartialEq, Copy, Clone)] -enum Response { - None = 0, - Short = 1, - Long = 3, +fn get_waitresp_val(rlen: ResponseLen) -> u8 { + match rlen { + common_cmd::ResponseLen::Zero => 0, + common_cmd::ResponseLen::R48 => 1, + common_cmd::ResponseLen::R136 => 3, + } } /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to @@ -300,18 +302,77 @@ impl Default for Config { } } -/// Sdmmc device -pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma = NoDma> { - _peri: PeripheralRef<'d, T>, - #[allow(unused)] - dma: PeripheralRef<'d, Dma>, +/// Peripheral that can be operated over SDMMC +#[derive(Clone, Copy, Debug)] +pub enum SdmmcPeripheral { + /// SD Card + SdCard(Card), + /// eMMC memory + Emmc(Emmc), +} - clk: PeripheralRef<'d, AnyPin>, - cmd: PeripheralRef<'d, AnyPin>, - d0: PeripheralRef<'d, AnyPin>, - d1: Option>, - d2: Option>, - d3: Option>, +impl SdmmcPeripheral { + /// Get this peripheral's address on the SDMMC bus + fn get_address(&self) -> u16 { + match self { + Self::SdCard(c) => c.rca, + Self::Emmc(e) => e.rca, + } + } + /// Is this a standard or high capacity peripheral? + fn get_capacity(&self) -> CardCapacity { + match self { + Self::SdCard(c) => c.card_type, + Self::Emmc(e) => e.capacity, + } + } + /// Size in bytes + fn size(&self) -> u64 { + match self { + // SDHC / SDXC / SDUC + Self::SdCard(c) => u64::from(c.csd.block_count()) * 512, + // capacity > 2GB + Self::Emmc(e) => u64::from(e.ext_csd.sector_count()) * 512, + } + } + + /// Get a mutable reference to the SD Card. + /// + /// Panics if there is another peripheral instead. + fn get_sd_card(&mut self) -> &mut Card { + match *self { + Self::SdCard(ref mut c) => c, + _ => unreachable!("SD only"), + } + } + + /// Get a mutable reference to the eMMC. + /// + /// Panics if there is another peripheral instead. + fn get_emmc(&mut self) -> &mut Emmc { + match *self { + Self::Emmc(ref mut e) => e, + _ => unreachable!("eMMC only"), + } + } +} + +/// Sdmmc device +pub struct Sdmmc<'d, T: Instance> { + _peri: Peri<'d, T>, + #[cfg(sdmmc_v1)] + dma: ChannelAndRequest<'d>, + + clk: Peri<'d, AnyPin>, + cmd: Peri<'d, AnyPin>, + d0: Peri<'d, AnyPin>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, config: Config, /// Current clock to card @@ -319,7 +380,7 @@ pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma = NoDma> { /// Current signalling scheme to card signalling: Signalling, /// Card - card: Option, + card: Option, /// An optional buffer to be used for commands /// This should be used if there are special memory location requirements for dma @@ -334,19 +395,17 @@ const CMD_AF: AfType = AfType::output_pull(OutputType::PushPull, Speed::VeryHigh const DATA_AF: AfType = CMD_AF; #[cfg(sdmmc_v1)] -impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { +impl<'d, T: Instance> Sdmmc<'d, T> { /// Create a new SDMMC driver, with 1 data lane. pub fn new_1bit( - sdmmc: impl Peripheral

+ 'd, + sdmmc: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - dma: impl Peripheral

+ 'd, - clk: impl Peripheral

> + 'd, - cmd: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, + dma: Peri<'d, impl SdmmcDma>, + clk: Peri<'d, impl CkPin>, + cmd: Peri<'d, impl CmdPin>, + d0: Peri<'d, impl D0Pin>, config: Config, ) -> Self { - into_ref!(clk, cmd, d0); - critical_section::with(|_| { clk.set_as_af(clk.af_num(), CLK_AF); cmd.set_as_af(cmd.af_num(), CMD_AF); @@ -355,10 +414,14 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { Self::new_inner( sdmmc, - dma, - clk.map_into(), - cmd.map_into(), - d0.map_into(), + new_dma_nonopt!(dma), + clk.into(), + cmd.into(), + d0.into(), + None, + None, + None, + None, None, None, None, @@ -368,19 +431,17 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { /// Create a new SDMMC driver, with 4 data lanes. pub fn new_4bit( - sdmmc: impl Peripheral

+ 'd, + sdmmc: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - dma: impl Peripheral

+ 'd, - clk: impl Peripheral

> + 'd, - cmd: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, + dma: Peri<'d, impl SdmmcDma>, + clk: Peri<'d, impl CkPin>, + cmd: Peri<'d, impl CmdPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, config: Config, ) -> Self { - into_ref!(clk, cmd, d0, d1, d2, d3); - critical_section::with(|_| { clk.set_as_af(clk.af_num(), CLK_AF); cmd.set_as_af(cmd.af_num(), CMD_AF); @@ -392,31 +453,83 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { Self::new_inner( sdmmc, - dma, - clk.map_into(), - cmd.map_into(), - d0.map_into(), - Some(d1.map_into()), - Some(d2.map_into()), - Some(d3.map_into()), + new_dma_nonopt!(dma), + clk.into(), + cmd.into(), + d0.into(), + Some(d1.into()), + Some(d2.into()), + Some(d3.into()), + None, + None, + None, + None, + config, + ) + } +} + +#[cfg(sdmmc_v1)] +impl<'d, T: Instance> Sdmmc<'d, T> { + /// Create a new SDMMC driver, with 8 data lanes. + pub fn new_8bit( + sdmmc: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + dma: Peri<'d, impl SdmmcDma>, + clk: Peri<'d, impl CkPin>, + cmd: Peri<'d, impl CmdPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + config: Config, + ) -> Self { + critical_section::with(|_| { + clk.set_as_af(clk.af_num(), CLK_AF); + cmd.set_as_af(cmd.af_num(), CMD_AF); + d0.set_as_af(d0.af_num(), DATA_AF); + d1.set_as_af(d1.af_num(), DATA_AF); + d2.set_as_af(d2.af_num(), DATA_AF); + d3.set_as_af(d3.af_num(), DATA_AF); + d4.set_as_af(d4.af_num(), DATA_AF); + d5.set_as_af(d5.af_num(), DATA_AF); + d6.set_as_af(d6.af_num(), DATA_AF); + d7.set_as_af(d7.af_num(), DATA_AF); + }); + + Self::new_inner( + sdmmc, + new_dma_nonopt!(dma), + clk.into(), + cmd.into(), + d0.into(), + Some(d1.into()), + Some(d2.into()), + Some(d3.into()), + Some(d4.into()), + Some(d5.into()), + Some(d6.into()), + Some(d7.into()), config, ) } } #[cfg(sdmmc_v2)] -impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { +impl<'d, T: Instance> Sdmmc<'d, T> { /// Create a new SDMMC driver, with 1 data lane. pub fn new_1bit( - sdmmc: impl Peripheral

+ 'd, + sdmmc: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - clk: impl Peripheral

> + 'd, - cmd: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, + clk: Peri<'d, impl CkPin>, + cmd: Peri<'d, impl CmdPin>, + d0: Peri<'d, impl D0Pin>, config: Config, ) -> Self { - into_ref!(clk, cmd, d0); - critical_section::with(|_| { clk.set_as_af(clk.af_num(), CLK_AF); cmd.set_as_af(cmd.af_num(), CMD_AF); @@ -425,10 +538,13 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { Self::new_inner( sdmmc, - NoDma.into_ref(), - clk.map_into(), - cmd.map_into(), - d0.map_into(), + clk.into(), + cmd.into(), + d0.into(), + None, + None, + None, + None, None, None, None, @@ -438,18 +554,16 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { /// Create a new SDMMC driver, with 4 data lanes. pub fn new_4bit( - sdmmc: impl Peripheral

+ 'd, + sdmmc: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - clk: impl Peripheral

> + 'd, - cmd: impl Peripheral

> + 'd, - d0: impl Peripheral

> + 'd, - d1: impl Peripheral

> + 'd, - d2: impl Peripheral

> + 'd, - d3: impl Peripheral

> + 'd, + clk: Peri<'d, impl CkPin>, + cmd: Peri<'d, impl CmdPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, config: Config, ) -> Self { - into_ref!(clk, cmd, d0, d1, d2, d3); - critical_section::with(|_| { clk.set_as_af(clk.af_num(), CLK_AF); cmd.set_as_af(cmd.af_num(), CMD_AF); @@ -461,32 +575,85 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { Self::new_inner( sdmmc, - NoDma.into_ref(), - clk.map_into(), - cmd.map_into(), - d0.map_into(), - Some(d1.map_into()), - Some(d2.map_into()), - Some(d3.map_into()), + clk.into(), + cmd.into(), + d0.into(), + Some(d1.into()), + Some(d2.into()), + Some(d3.into()), + None, + None, + None, + None, config, ) } } -impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { - fn new_inner( - sdmmc: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, - clk: PeripheralRef<'d, AnyPin>, - cmd: PeripheralRef<'d, AnyPin>, - d0: PeripheralRef<'d, AnyPin>, - d1: Option>, - d2: Option>, - d3: Option>, +#[cfg(sdmmc_v2)] +impl<'d, T: Instance> Sdmmc<'d, T> { + /// Create a new SDMMC driver, with 8 data lanes. + pub fn new_8bit( + sdmmc: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + clk: Peri<'d, impl CkPin>, + cmd: Peri<'d, impl CmdPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, config: Config, ) -> Self { - into_ref!(sdmmc, dma); + critical_section::with(|_| { + clk.set_as_af(clk.af_num(), CLK_AF); + cmd.set_as_af(cmd.af_num(), CMD_AF); + d0.set_as_af(d0.af_num(), DATA_AF); + d1.set_as_af(d1.af_num(), DATA_AF); + d2.set_as_af(d2.af_num(), DATA_AF); + d3.set_as_af(d3.af_num(), DATA_AF); + d4.set_as_af(d4.af_num(), DATA_AF); + d5.set_as_af(d5.af_num(), DATA_AF); + d6.set_as_af(d6.af_num(), DATA_AF); + d7.set_as_af(d7.af_num(), DATA_AF); + }); + Self::new_inner( + sdmmc, + clk.into(), + cmd.into(), + d0.into(), + Some(d1.into()), + Some(d2.into()), + Some(d3.into()), + Some(d4.into()), + Some(d5.into()), + Some(d6.into()), + Some(d7.into()), + config, + ) + } +} + +impl<'d, T: Instance> Sdmmc<'d, T> { + fn new_inner( + sdmmc: Peri<'d, T>, + #[cfg(sdmmc_v1)] dma: ChannelAndRequest<'d>, + clk: Peri<'d, AnyPin>, + cmd: Peri<'d, AnyPin>, + d0: Peri<'d, AnyPin>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + config: Config, + ) -> Self { rcc::enable_and_reset::(); T::Interrupt::unpend(); @@ -514,6 +681,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Self { _peri: sdmmc, + #[cfg(sdmmc_v1)] dma, clk, @@ -522,6 +690,10 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { d1, d2, d3, + d4, + d5, + d6, + d7, config, clock: SD_INIT_FREQ, @@ -567,7 +739,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { #[allow(unused_variables)] fn prepare_datapath_read<'a>( config: &Config, - dma: &'a mut PeripheralRef<'d, Dma>, + #[cfg(sdmmc_v1)] dma: &'a mut ChannelAndRequest<'d>, buffer: &'a mut [u32], length_bytes: u32, block_size: u8, @@ -583,16 +755,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { regs.dlenr().write(|w| w.set_datalength(length_bytes)); #[cfg(sdmmc_v1)] - let transfer = unsafe { - let request = dma.request(); - Transfer::new_read( - dma, - request, - regs.fifor().as_ptr() as *mut u32, - buffer, - DMA_TRANSFER_OPTIONS, - ) - }; + let transfer = unsafe { dma.read(regs.fifor().as_ptr() as *mut u32, buffer, DMA_TRANSFER_OPTIONS) }; #[cfg(sdmmc_v2)] let transfer = { regs.idmabase0r().write(|w| w.set_idmabase0(buffer.as_mut_ptr() as u32)); @@ -632,14 +795,8 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { #[cfg(sdmmc_v1)] let transfer = unsafe { - let request = self.dma.request(); - Transfer::new_write( - &mut self.dma, - request, - buffer, - regs.fifor().as_ptr() as *mut u32, - DMA_TRANSFER_OPTIONS, - ) + self.dma + .write(buffer, regs.fifor().as_ptr() as *mut u32, DMA_TRANSFER_OPTIONS) }; #[cfg(sdmmc_v2)] let transfer = { @@ -707,168 +864,29 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Ok(()) } - /// Switch mode using CMD6. - /// - /// Attempt to set a new signalling mode. The selected - /// signalling mode is returned. Expects the current clock - /// frequency to be > 12.5MHz. - async fn switch_signalling_mode(&mut self, signalling: Signalling) -> Result { - // NB PLSS v7_10 4.3.10.4: "the use of SET_BLK_LEN command is not - // necessary" - - let set_function = 0x8000_0000 - | match signalling { - // See PLSS v7_10 Table 4-11 - Signalling::DDR50 => 0xFF_FF04, - Signalling::SDR104 => 0xFF_1F03, - Signalling::SDR50 => 0xFF_1F02, - Signalling::SDR25 => 0xFF_FF01, - Signalling::SDR12 => 0xFF_FF00, - }; - - let status = match self.cmd_block.as_deref_mut() { - Some(x) => x, - None => &mut CmdBlock::new(), - }; - - // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = T::regs(); - let on_drop = OnDrop::new(|| Self::on_drop()); - - let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, status.as_mut(), 64, 6); - InterruptHandler::::data_interrupts(true); - Self::cmd(Cmd::cmd6(set_function), true)?; // CMD6 - - let res = poll_fn(|cx| { - T::state().register(cx.waker()); - let status = regs.star().read(); - - if status.dcrcfail() { - return Poll::Ready(Err(Error::Crc)); - } - if status.dtimeout() { - return Poll::Ready(Err(Error::Timeout)); - } - #[cfg(sdmmc_v1)] - if status.stbiterr() { - return Poll::Ready(Err(Error::StBitErr)); - } - if status.dataend() { - return Poll::Ready(Ok(())); - } - Poll::Pending - }) - .await; - Self::clear_interrupt_flags(); - - // Host is allowed to use the new functions at least 8 - // clocks after the end of the switch command - // transaction. We know the current clock period is < 80ns, - // so a total delay of 640ns is required here - for _ in 0..300 { - cortex_m::asm::nop(); - } - - match res { - Ok(_) => { - on_drop.defuse(); - Self::stop_datapath(); - drop(transfer); - - // Function Selection of Function Group 1 - let selection = (u32::from_be(status[4]) >> 24) & 0xF; - - match selection { - 0 => Ok(Signalling::SDR12), - 1 => Ok(Signalling::SDR25), - 2 => Ok(Signalling::SDR50), - 3 => Ok(Signalling::SDR104), - 4 => Ok(Signalling::DDR50), - _ => Err(Error::UnsupportedCardType), - } - } - Err(e) => Err(e), - } - } - /// Query the card status (CMD13, returns R1) - fn read_status(&self, card: &Card) -> Result { + fn read_status(&self, card: &SdmmcPeripheral) -> Result, Error> + where + CardStatus: From, + { let regs = T::regs(); - let rca = card.rca; + let rca = card.get_address(); - Self::cmd(Cmd::card_status(rca << 16), false)?; // CMD13 + Self::cmd(common_cmd::card_status(rca, false), false)?; // CMD13 let r1 = regs.respr(0).read().cardstatus(); Ok(r1.into()) } - /// Reads the SD Status (ACMD13) - async fn read_sd_status(&mut self) -> Result<(), Error> { - let card = self.card.as_mut().ok_or(Error::NoCard)?; - let rca = card.rca; - - let cmd_block = match self.cmd_block.as_deref_mut() { - Some(x) => x, - None => &mut CmdBlock::new(), - }; - - Self::cmd(Cmd::set_block_length(64), false)?; // CMD16 - Self::cmd(Cmd::app_cmd(rca << 16), false)?; // APP - - let status = cmd_block; - - // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = T::regs(); - let on_drop = OnDrop::new(|| Self::on_drop()); - - let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, status.as_mut(), 64, 6); - InterruptHandler::::data_interrupts(true); - Self::cmd(Cmd::card_status(0), true)?; - - let res = poll_fn(|cx| { - T::state().register(cx.waker()); - let status = regs.star().read(); - - if status.dcrcfail() { - return Poll::Ready(Err(Error::Crc)); - } - if status.dtimeout() { - return Poll::Ready(Err(Error::Timeout)); - } - #[cfg(sdmmc_v1)] - if status.stbiterr() { - return Poll::Ready(Err(Error::StBitErr)); - } - if status.dataend() { - return Poll::Ready(Ok(())); - } - Poll::Pending - }) - .await; - Self::clear_interrupt_flags(); - - if res.is_ok() { - on_drop.defuse(); - Self::stop_datapath(); - drop(transfer); - - for byte in status.iter_mut() { - *byte = u32::from_be(*byte); - } - self.card.as_mut().unwrap().status = status.0.into(); - } - res - } - /// Select one card and place it into the _Tranfer State_ /// /// If `None` is specifed for `card`, all cards are put back into /// _Stand-by State_ - fn select_card(&self, card: Option<&Card>) -> Result<(), Error> { + fn select_card(&self, rca: Option) -> Result<(), Error> { // Determine Relative Card Address (RCA) of given card - let rca = card.map(|c| c.rca << 16).unwrap_or(0); + let rca = rca.unwrap_or(0); - let r = Self::cmd(Cmd::sel_desel_card(rca), false); + let r = Self::cmd(common_cmd::select_card(rca), false); match (r, rca) { (Err(Error::Timeout), 0) => Ok(()), _ => r, @@ -909,63 +927,9 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { }); } - async fn get_scr(&mut self, card: &mut Card) -> Result<(), Error> { - // Read the the 64-bit SCR register - Self::cmd(Cmd::set_block_length(8), false)?; // CMD16 - Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; - - let cmd_block = match self.cmd_block.as_deref_mut() { - Some(x) => x, - None => &mut CmdBlock::new(), - }; - let scr = &mut cmd_block.0[..2]; - - // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = T::regs(); - let on_drop = OnDrop::new(|| Self::on_drop()); - - let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, scr, 8, 3); - InterruptHandler::::data_interrupts(true); - Self::cmd(Cmd::cmd51(), true)?; - - let res = poll_fn(|cx| { - T::state().register(cx.waker()); - let status = regs.star().read(); - - if status.dcrcfail() { - return Poll::Ready(Err(Error::Crc)); - } - if status.dtimeout() { - return Poll::Ready(Err(Error::Timeout)); - } - #[cfg(sdmmc_v1)] - if status.stbiterr() { - return Poll::Ready(Err(Error::StBitErr)); - } - if status.dataend() { - return Poll::Ready(Ok(())); - } - Poll::Pending - }) - .await; - Self::clear_interrupt_flags(); - - if res.is_ok() { - on_drop.defuse(); - Self::stop_datapath(); - drop(transfer); - - unsafe { - let scr_bytes = &*(&scr as *const _ as *const [u8; 8]); - card.scr = SCR(u64::from_be_bytes(*scr_bytes)); - } - } - res - } - /// Send command to card #[allow(unused_variables)] - fn cmd(cmd: Cmd, data: bool) -> Result<(), Error> { + fn cmd(cmd: Cmd, data: bool) -> Result<(), Error> { let regs = T::regs(); Self::clear_interrupt_flags(); @@ -978,7 +942,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { // Command index and start CP State Machine regs.cmdr().write(|w| { w.set_waitint(false); - w.set_waitresp(cmd.resp as u8); + w.set_waitresp(get_waitresp_val(cmd.response_len())); w.set_cmdindex(cmd.cmd); w.set_cpsmen(true); @@ -993,7 +957,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { }); let mut status; - if cmd.resp == Response::None { + if cmd.response_len() == ResponseLen::Zero { // Wait for CMDSENT or a timeout while { status = regs.star().read(); @@ -1029,7 +993,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { // Command index and start CP State Machine regs.cmdr().write(|w| { w.set_waitint(false); - w.set_waitresp(Response::Short as u8); + w.set_waitresp(get_waitresp_val(ResponseLen::R48)); w.set_cmdindex(12); w.set_cpsmen(true); @@ -1048,157 +1012,44 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Self::stop_datapath(); } - /// Initializes card (if present) and sets the bus at the specified frequency. - pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { + /// Wait for a previously started datapath transfer to complete from an interrupt. + #[inline] + async fn complete_datapath_transfer() -> Result<(), Error> { let regs = T::regs(); - let ker_ck = T::frequency(); - let bus_width = match self.d3.is_some() { - true => BusWidth::Four, - false => BusWidth::One, - }; + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); - // While the SD/SDIO card or eMMC is in identification mode, - // the SDMMC_CK frequency must be no more than 400 kHz. - let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); - self.clock = init_clock; - - // CPSMACT and DPSMACT must be 0 to set WIDBUS - Self::wait_idle(); - - regs.clkcr().modify(|w| { - w.set_widbus(0); - w.set_clkdiv(clkdiv); + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } + if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } + if status.txunderr() { + return Poll::Ready(Err(Error::Underrun)); + } #[cfg(sdmmc_v1)] - w.set_bypass(_bypass); - }); - - regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); - Self::cmd(Cmd::idle(), false)?; - - // Check if cards supports CMD8 (with pattern) - Self::cmd(Cmd::hs_send_ext_csd(0x1AA), false)?; - let r1 = regs.respr(0).read().cardstatus(); - - let mut card = if r1 == 0x1AA { - // Card echoed back the pattern. Must be at least v2 - Card::default() - } else { - return Err(Error::UnsupportedCardVersion); - }; - - let ocr = loop { - // Signal that next command is a app command - Self::cmd(Cmd::app_cmd(0), false)?; // CMD55 - - let arg = CmdAppOper::VOLTAGE_WINDOW_SD as u32 - | CmdAppOper::HIGH_CAPACITY as u32 - | CmdAppOper::SD_SWITCH_1_8V_CAPACITY as u32; - - // Initialize card - match Self::cmd(Cmd::app_op_cmd(arg), false) { - // ACMD41 - Ok(_) => (), - Err(Error::Crc) => (), - Err(err) => return Err(err), + if status.stbiterr() { + return Poll::Ready(Err(Error::StBitErr)); } - let ocr: OCR = regs.respr(0).read().cardstatus().into(); - if !ocr.is_busy() { - // Power up done - break ocr; + if status.dataend() { + return Poll::Ready(Ok(())); } - }; + Poll::Pending + }) + .await; - if ocr.high_capacity() { - // Card is SDHC or SDXC or SDUC - card.card_type = CardCapacity::SDHC; - } else { - card.card_type = CardCapacity::SDSC; - } - card.ocr = ocr; + Self::clear_interrupt_flags(); - Self::cmd(Cmd::all_send_cid(), false)?; // CMD2 - let cid0 = regs.respr(0).read().cardstatus() as u128; - let cid1 = regs.respr(1).read().cardstatus() as u128; - let cid2 = regs.respr(2).read().cardstatus() as u128; - let cid3 = regs.respr(3).read().cardstatus() as u128; - let cid = (cid0 << 96) | (cid1 << 64) | (cid2 << 32) | (cid3); - card.cid = cid.into(); - - Self::cmd(Cmd::send_rel_addr(), false)?; - card.rca = regs.respr(0).read().cardstatus() >> 16; - - Self::cmd(Cmd::send_csd(card.rca << 16), false)?; - let csd0 = regs.respr(0).read().cardstatus() as u128; - let csd1 = regs.respr(1).read().cardstatus() as u128; - let csd2 = regs.respr(2).read().cardstatus() as u128; - let csd3 = regs.respr(3).read().cardstatus() as u128; - let csd = (csd0 << 96) | (csd1 << 64) | (csd2 << 32) | (csd3); - card.csd = csd.into(); - - self.select_card(Some(&card))?; - - self.get_scr(&mut card).await?; - - // Set bus width - let (width, acmd_arg) = match bus_width { - BusWidth::Eight => unimplemented!(), - BusWidth::Four if card.scr.bus_width_four() => (BusWidth::Four, 2), - _ => (BusWidth::One, 0), - }; - Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; - Self::cmd(Cmd::cmd6(acmd_arg), false)?; - - // CPSMACT and DPSMACT must be 0 to set WIDBUS - Self::wait_idle(); - - regs.clkcr().modify(|w| { - w.set_widbus(match width { - BusWidth::One => 0, - BusWidth::Four => 1, - BusWidth::Eight => 2, - _ => panic!("Invalid Bus Width"), - }) - }); - - // Set Clock - if freq.0 <= 25_000_000 { - // Final clock frequency - self.clkcr_set_clkdiv(freq.0, width)?; - } else { - // Switch to max clock for SDR12 - self.clkcr_set_clkdiv(25_000_000, width)?; - } - - self.card = Some(card); - - // Read status - self.read_sd_status().await?; - - if freq.0 > 25_000_000 { - // Switch to SDR25 - self.signalling = self.switch_signalling_mode(Signalling::SDR25).await?; - - if self.signalling == Signalling::SDR25 { - // Set final clock frequency - self.clkcr_set_clkdiv(freq.0, width)?; - - if self.read_status(&card)?.state() != CurrentState::Transfer { - return Err(Error::SignalingSwitchFailed); - } - } - } - - // Read status after signalling change - self.read_sd_status().await?; - - Ok(()) + res } /// Read a data block. #[inline] pub async fn read_block(&mut self, block_idx: u32, buffer: &mut DataBlock) -> Result<(), Error> { - let card_capacity = self.card()?.card_type; + let card_capacity = self.card()?.get_capacity(); // NOTE(unsafe) DataBlock uses align 4 let buffer = unsafe { &mut *((&mut buffer.0) as *mut [u8; 512] as *mut [u32; 128]) }; @@ -1206,17 +1057,68 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { // Always read 1 block of 512 bytes // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes let address = match card_capacity { - CardCapacity::SDSC => block_idx * 512, + CardCapacity::StandardCapacity => block_idx * 512, _ => block_idx, }; - Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + Self::cmd(common_cmd::set_block_length(512), false)?; // CMD16 + + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read( + &self.config, + #[cfg(sdmmc_v1)] + &mut self.dma, + buffer, + 512, + 9, + ); + InterruptHandler::::data_interrupts(true); + Self::cmd(common_cmd::read_single_block(address), true)?; + + let res = Self::complete_datapath_transfer().await; + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + } + res + } + + /// Read multiple data blocks. + #[inline] + pub async fn read_blocks(&mut self, block_idx: u32, blocks: &mut [DataBlock]) -> Result<(), Error> { + let card_capacity = self.card()?.get_capacity(); + + // NOTE(unsafe) reinterpret buffer as &mut [u32] + let buffer = unsafe { + let ptr = blocks.as_mut_ptr() as *mut u32; + let len = blocks.len() * 128; + core::slice::from_raw_parts_mut(ptr, len) + }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card_capacity { + CardCapacity::StandardCapacity => block_idx * 512, + _ => block_idx, + }; + Self::cmd(common_cmd::set_block_length(512), false)?; // CMD16 let regs = T::regs(); let on_drop = OnDrop::new(|| Self::on_drop()); - let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, buffer, 512, 9); + let transfer = Self::prepare_datapath_read( + &self.config, + #[cfg(sdmmc_v1)] + &mut self.dma, + buffer, + 512 * blocks.len() as u32, + 9, + ); InterruptHandler::::data_interrupts(true); - Self::cmd(Cmd::read_single_block(address), true)?; + + Self::cmd(common_cmd::read_multiple_blocks(address), true)?; let res = poll_fn(|cx| { T::state().register(cx.waker()); @@ -1238,6 +1140,8 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Poll::Pending }) .await; + + Self::cmd(common_cmd::stop_transmission(), false)?; // CMD12 Self::clear_interrupt_flags(); if res.is_ok() { @@ -1256,28 +1160,92 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { let buffer = unsafe { &*((&buffer.0) as *const [u8; 512] as *const [u32; 128]) }; // Always read 1 block of 512 bytes - // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes - let address = match card.card_type { - CardCapacity::SDSC => block_idx * 512, + // cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card.get_capacity() { + CardCapacity::StandardCapacity => block_idx * 512, _ => block_idx, }; - Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + Self::cmd(common_cmd::set_block_length(512), false)?; // CMD16 - let regs = T::regs(); let on_drop = OnDrop::new(|| Self::on_drop()); // sdmmc_v1 uses different cmd/dma order than v2, but only for writes #[cfg(sdmmc_v1)] - Self::cmd(Cmd::write_single_block(address), true)?; + Self::cmd(common_cmd::write_single_block(address), true)?; let transfer = self.prepare_datapath_write(buffer, 512, 9); InterruptHandler::::data_interrupts(true); #[cfg(sdmmc_v2)] - Self::cmd(Cmd::write_single_block(address), true)?; + Self::cmd(common_cmd::write_single_block(address), true)?; + + let res = Self::complete_datapath_transfer().await; + + match res { + Ok(_) => { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + // TODO: Make this configurable + let mut timeout: u32 = 0x00FF_FFFF; + + let card = self.card.as_ref().unwrap(); + while timeout > 0 { + let ready_for_data = match card { + SdmmcPeripheral::Emmc(_) => self.read_status::(card)?.ready_for_data(), + SdmmcPeripheral::SdCard(_) => self.read_status::(card)?.ready_for_data(), + }; + + if ready_for_data { + return Ok(()); + } + timeout -= 1; + } + Err(Error::SoftwareTimeout) + } + Err(e) => Err(e), + } + } + + /// Write multiple data blocks. + pub async fn write_blocks(&mut self, block_idx: u32, blocks: &[DataBlock]) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?; + + // NOTE(unsafe) reinterpret buffer as &[u32] + let buffer = unsafe { + let ptr = blocks.as_ptr() as *const u32; + let len = blocks.len() * 128; + core::slice::from_raw_parts(ptr, len) + }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card.get_capacity() { + CardCapacity::StandardCapacity => block_idx * 512, + _ => block_idx, + }; + + Self::cmd(common_cmd::set_block_length(512), false)?; // CMD16 + + let block_count = blocks.len(); + + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + #[cfg(sdmmc_v1)] + Self::cmd(common_cmd::write_multiple_blocks(address), true)?; // CMD25 + + // Setup write command + let transfer = self.prepare_datapath_write(buffer, 512 * block_count as u32, 9); + InterruptHandler::::data_interrupts(true); + + #[cfg(sdmmc_v2)] + Self::cmd(common_cmd::write_multiple_blocks(address), true)?; // CMD25 let res = poll_fn(|cx| { T::state().register(cx.waker()); + let status = regs.star().read(); if status.dcrcfail() { @@ -1286,6 +1254,9 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { if status.dtimeout() { return Poll::Ready(Err(Error::Timeout)); } + if status.txunderr() { + return Poll::Ready(Err(Error::Underrun)); + } #[cfg(sdmmc_v1)] if status.stbiterr() { return Poll::Ready(Err(Error::StBitErr)); @@ -1293,9 +1264,12 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { if status.dataend() { return Poll::Ready(Ok(())); } + Poll::Pending }) .await; + + Self::cmd(common_cmd::stop_transmission(), false)?; // CMD12 Self::clear_interrupt_flags(); match res { @@ -1326,10 +1300,10 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { /// /// # Errors /// - /// Returns Error::NoCard if [`init_card`](#method.init_card) - /// has not previously succeeded + /// Returns Error::NoCard if [`init_sd_card`](#method.init_sd_card) or + /// [`init_emmc`](#method.init_emmc) has not previously succeeded #[inline] - pub fn card(&self) -> Result<&Card, Error> { + pub fn card(&self) -> Result<&SdmmcPeripheral, Error> { self.card.as_ref().ok_or(Error::NoCard) } @@ -1345,9 +1319,460 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { pub fn set_cmd_block(&mut self, cmd_block: &'d mut CmdBlock) { self.cmd_block = Some(cmd_block) } + + async fn init_internal(&mut self, freq: Hertz, mut card: SdmmcPeripheral) -> Result<(), Error> { + let regs = T::regs(); + let ker_ck = T::frequency(); + + let bus_width = match (self.d3.is_some(), self.d7.is_some()) { + (true, true) => { + if matches!(card, SdmmcPeripheral::SdCard(_)) { + return Err(Error::BusWidth); + } + BusWidth::Eight + } + (true, false) => BusWidth::Four, + _ => BusWidth::One, + }; + + // While the SD/SDIO card or eMMC is in identification mode, + // the SDMMC_CK frequency must be no more than 400 kHz. + let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); + self.clock = init_clock; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(0); + w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); + }); + + regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); + Self::cmd(common_cmd::idle(), false)?; + + match card { + SdmmcPeripheral::SdCard(ref mut card) => { + // Check if cards supports CMD8 (with pattern) + Self::cmd(sd_cmd::send_if_cond(1, 0xAA), false)?; + let cic = CIC::from(regs.respr(0).read().cardstatus()); + + if cic.pattern() != 0xAA { + return Err(Error::UnsupportedCardVersion); + } + + if cic.voltage_accepted() & 1 == 0 { + return Err(Error::UnsupportedVoltage); + } + + let ocr = loop { + // Signal that next command is a app command + Self::cmd(common_cmd::app_cmd(0), false)?; // CMD55 + + // 3.2-3.3V + let voltage_window = 1 << 5; + // Initialize card + match Self::cmd(sd_cmd::sd_send_op_cond(true, false, true, voltage_window), false) { + // ACMD41 + Ok(_) => (), + Err(Error::Crc) => (), + Err(err) => return Err(err), + } + let ocr: OCR = regs.respr(0).read().cardstatus().into(); + if !ocr.is_busy() { + // Power up done + break ocr; + } + }; + + if ocr.high_capacity() { + // Card is SDHC or SDXC or SDUC + card.card_type = CardCapacity::HighCapacity; + } else { + card.card_type = CardCapacity::StandardCapacity; + } + card.ocr = ocr; + } + SdmmcPeripheral::Emmc(ref mut emmc) => { + let ocr = loop { + let high_voltage = 0b0 << 7; + let access_mode = 0b10 << 29; + let op_cond = high_voltage | access_mode | 0b1_1111_1111 << 15; + // Initialize card + match Self::cmd(emmc_cmd::send_op_cond(op_cond), false) { + Ok(_) => (), + Err(Error::Crc) => (), + Err(err) => return Err(err), + } + let ocr: OCR = regs.respr(0).read().cardstatus().into(); + if !ocr.is_busy() { + // Power up done + break ocr; + } + }; + + emmc.capacity = if ocr.access_mode() == 0b10 { + // Card is SDHC or SDXC or SDUC + CardCapacity::HighCapacity + } else { + CardCapacity::StandardCapacity + }; + emmc.ocr = ocr; + } + } + + Self::cmd(common_cmd::all_send_cid(), false)?; // CMD2 + let cid0 = regs.respr(0).read().cardstatus() as u128; + let cid1 = regs.respr(1).read().cardstatus() as u128; + let cid2 = regs.respr(2).read().cardstatus() as u128; + let cid3 = regs.respr(3).read().cardstatus() as u128; + let cid = (cid0 << 96) | (cid1 << 64) | (cid2 << 32) | (cid3); + + match card { + SdmmcPeripheral::SdCard(ref mut card) => { + card.cid = cid.into(); + + Self::cmd(sd_cmd::send_relative_address(), false)?; + let rca = RCA::::from(regs.respr(0).read().cardstatus()); + card.rca = rca.address(); + } + SdmmcPeripheral::Emmc(ref mut emmc) => { + emmc.cid = cid.into(); + + emmc.rca = 1u16.into(); + Self::cmd(emmc_cmd::assign_relative_address(emmc.rca), false)?; + } + } + + Self::cmd(common_cmd::send_csd(card.get_address()), false)?; + let csd0 = regs.respr(0).read().cardstatus() as u128; + let csd1 = regs.respr(1).read().cardstatus() as u128; + let csd2 = regs.respr(2).read().cardstatus() as u128; + let csd3 = regs.respr(3).read().cardstatus() as u128; + let csd = (csd0 << 96) | (csd1 << 64) | (csd2 << 32) | (csd3); + + self.select_card(Some(card.get_address()))?; + + let bus_width = match card { + SdmmcPeripheral::SdCard(ref mut card) => { + card.csd = csd.into(); + + self.get_scr(card).await?; + + if !card.scr.bus_width_four() { + BusWidth::One + } else { + BusWidth::Four + } + } + SdmmcPeripheral::Emmc(ref mut emmc) => { + emmc.csd = csd.into(); + + bus_width + } + }; + + // Set bus width + let widbus = match bus_width { + BusWidth::Eight => 2, + BusWidth::Four => 1, + BusWidth::One => 0, + _ => unreachable!(), + }; + + match card { + SdmmcPeripheral::SdCard(ref mut card) => { + let acmd_arg = match bus_width { + BusWidth::Four if card.scr.bus_width_four() => 2, + _ => 0, + }; + Self::cmd(common_cmd::app_cmd(card.rca), false)?; + Self::cmd(sd_cmd::cmd6(acmd_arg), false)?; + } + SdmmcPeripheral::Emmc(_) => { + // Write bus width to ExtCSD byte 183 + Self::cmd( + emmc_cmd::modify_ext_csd(emmc_cmd::AccessMode::WriteByte, 183, widbus), + false, + )?; + + // Wait for ready after R1b response + loop { + let status = self.read_status::(&card)?; + + if status.ready_for_data() { + break; + } + } + } + } + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| w.set_widbus(widbus)); + + // Set Clock + if freq.0 <= 25_000_000 { + // Final clock frequency + self.clkcr_set_clkdiv(freq.0, bus_width)?; + } else { + // Switch to max clock for SDR12 + self.clkcr_set_clkdiv(25_000_000, bus_width)?; + } + + self.card = Some(card); + + match card { + SdmmcPeripheral::SdCard(_) => { + // Read status + self.read_sd_status().await?; + + if freq.0 > 25_000_000 { + // Switch to SDR25 + self.signalling = self.switch_signalling_mode(Signalling::SDR25).await?; + + if self.signalling == Signalling::SDR25 { + // Set final clock frequency + self.clkcr_set_clkdiv(freq.0, bus_width)?; + + if self.read_status::(self.card.as_ref().unwrap())?.state() != CurrentState::Transfer { + return Err(Error::SignalingSwitchFailed); + } + } + } + + // Read status after signalling change + self.read_sd_status().await?; + } + SdmmcPeripheral::Emmc(_) => { + self.read_ext_csd().await?; + } + } + + Ok(()) + } + + /// Initializes card (if present) and sets the bus at the specified frequency. + /// + /// SD only. + pub async fn init_sd_card(&mut self, freq: Hertz) -> Result<(), Error> { + self.init_internal(freq, SdmmcPeripheral::SdCard(Card::default())).await + } + + /// Switch mode using CMD6. + /// + /// Attempt to set a new signalling mode. The selected + /// signalling mode is returned. Expects the current clock + /// frequency to be > 12.5MHz. + /// + /// SD only. + async fn switch_signalling_mode(&mut self, signalling: Signalling) -> Result { + let _ = self.card.as_mut().ok_or(Error::NoCard)?.get_sd_card(); + // NB PLSS v7_10 4.3.10.4: "the use of SET_BLK_LEN command is not + // necessary" + + let set_function = 0x8000_0000 + | match signalling { + // See PLSS v7_10 Table 4-11 + Signalling::DDR50 => 0xFF_FF04, + Signalling::SDR104 => 0xFF_1F03, + Signalling::SDR50 => 0xFF_1F02, + Signalling::SDR25 => 0xFF_FF01, + Signalling::SDR12 => 0xFF_FF00, + }; + + let status = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + + // Arm `OnDrop` after the buffer, so it will be dropped first + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read( + &self.config, + #[cfg(sdmmc_v1)] + &mut self.dma, + status.as_mut(), + 64, + 6, + ); + InterruptHandler::::data_interrupts(true); + Self::cmd(sd_cmd::cmd6(set_function), true)?; // CMD6 + + let res = Self::complete_datapath_transfer().await; + + // Host is allowed to use the new functions at least 8 + // clocks after the end of the switch command + // transaction. We know the current clock period is < 80ns, + // so a total delay of 640ns is required here + for _ in 0..300 { + cortex_m::asm::nop(); + } + + match res { + Ok(_) => { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + // Function Selection of Function Group 1 + let selection = (u32::from_be(status[4]) >> 24) & 0xF; + + match selection { + 0 => Ok(Signalling::SDR12), + 1 => Ok(Signalling::SDR25), + 2 => Ok(Signalling::SDR50), + 3 => Ok(Signalling::SDR104), + 4 => Ok(Signalling::DDR50), + _ => Err(Error::UnsupportedCardType), + } + } + Err(e) => Err(e), + } + } + + /// Reads the SCR register. + /// + /// SD only. + async fn get_scr(&mut self, card: &mut Card) -> Result<(), Error> { + // Read the 64-bit SCR register + Self::cmd(common_cmd::set_block_length(8), false)?; // CMD16 + Self::cmd(common_cmd::app_cmd(card.rca), false)?; + + let cmd_block = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + let scr = &mut cmd_block.0[..2]; + + // Arm `OnDrop` after the buffer, so it will be dropped first + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read( + &self.config, + #[cfg(sdmmc_v1)] + &mut self.dma, + scr, + 8, + 3, + ); + InterruptHandler::::data_interrupts(true); + Self::cmd(sd_cmd::send_scr(), true)?; + + let res = Self::complete_datapath_transfer().await; + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + unsafe { + let scr_bytes = &*(&scr as *const _ as *const [u8; 8]); + card.scr = SCR(u64::from_be_bytes(*scr_bytes)); + } + } + res + } + + /// Reads the SD Status (ACMD13) + /// + /// SD only. + async fn read_sd_status(&mut self) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?.get_sd_card(); + let rca = card.rca; + + let cmd_block = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + + Self::cmd(common_cmd::set_block_length(64), false)?; // CMD16 + Self::cmd(common_cmd::app_cmd(rca), false)?; // APP + + let status = cmd_block; + + // Arm `OnDrop` after the buffer, so it will be dropped first + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read( + &self.config, + #[cfg(sdmmc_v1)] + &mut self.dma, + status.as_mut(), + 64, + 6, + ); + InterruptHandler::::data_interrupts(true); + Self::cmd(sd_cmd::sd_status(), true)?; + + let res = Self::complete_datapath_transfer().await; + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + for byte in status.iter_mut() { + *byte = u32::from_be(*byte); + } + card.status = status.0.into(); + } + res + } + + /// Initializes eMMC and sets the bus at the specified frequency. + /// + /// eMMC only. + pub async fn init_emmc(&mut self, freq: Hertz) -> Result<(), Error> { + self.init_internal(freq, SdmmcPeripheral::Emmc(Emmc::default())).await + } + + /// Gets the EXT_CSD register. + /// + /// eMMC only. + async fn read_ext_csd(&mut self) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?.get_emmc(); + + // Note: cmd_block can't be used because ExtCSD is too long to fit. + let mut data_block = DataBlock([0u8; 512]); + + // NOTE(unsafe) DataBlock uses align 4 + let buffer = unsafe { &mut *((&mut data_block.0) as *mut [u8; 512] as *mut [u32; 128]) }; + + Self::cmd(common_cmd::set_block_length(512), false).unwrap(); // CMD16 + + // Arm `OnDrop` after the buffer, so it will be dropped first + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = Self::prepare_datapath_read( + &self.config, + #[cfg(sdmmc_v1)] + &mut self.dma, + buffer, + 512, + 9, + ); + InterruptHandler::::data_interrupts(true); + Self::cmd(emmc_cmd::send_ext_csd(), true)?; + + let res = Self::complete_datapath_transfer().await; + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + card.ext_csd = unsafe { core::mem::transmute::<_, [u32; 128]>(data_block.0) }.into(); + } + res + } } -impl<'d, T: Instance, Dma: SdmmcDma + 'd> Drop for Sdmmc<'d, T, Dma> { +impl<'d, T: Instance> Drop for Sdmmc<'d, T> { fn drop(&mut self) { T::Interrupt::disable(); Self::on_drop(); @@ -1365,97 +1790,22 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Drop for Sdmmc<'d, T, Dma> { if let Some(x) = &mut self.d3 { x.set_as_disconnected(); } + if let Some(x) = &mut self.d4 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d5 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d6 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d7 { + x.set_as_disconnected(); + } }); } } -/// SD card Commands -impl Cmd { - const fn new(cmd: u8, arg: u32, resp: Response) -> Cmd { - Cmd { cmd, arg, resp } - } - - /// CMD0: Idle - const fn idle() -> Cmd { - Cmd::new(0, 0, Response::None) - } - - /// CMD2: Send CID - const fn all_send_cid() -> Cmd { - Cmd::new(2, 0, Response::Long) - } - - /// CMD3: Send Relative Address - const fn send_rel_addr() -> Cmd { - Cmd::new(3, 0, Response::Short) - } - - /// CMD6: Switch Function Command - /// ACMD6: Bus Width - const fn cmd6(arg: u32) -> Cmd { - Cmd::new(6, arg, Response::Short) - } - - /// CMD7: Select one card and put it into the _Tranfer State_ - const fn sel_desel_card(rca: u32) -> Cmd { - Cmd::new(7, rca, Response::Short) - } - - /// CMD8: - const fn hs_send_ext_csd(arg: u32) -> Cmd { - Cmd::new(8, arg, Response::Short) - } - - /// CMD9: - const fn send_csd(rca: u32) -> Cmd { - Cmd::new(9, rca, Response::Long) - } - - /// CMD12: - //const fn stop_transmission() -> Cmd { - // Cmd::new(12, 0, Response::Short) - //} - - /// CMD13: Ask card to send status register - /// ACMD13: SD Status - const fn card_status(rca: u32) -> Cmd { - Cmd::new(13, rca, Response::Short) - } - - /// CMD16: - const fn set_block_length(blocklen: u32) -> Cmd { - Cmd::new(16, blocklen, Response::Short) - } - - /// CMD17: Block Read - const fn read_single_block(addr: u32) -> Cmd { - Cmd::new(17, addr, Response::Short) - } - - /// CMD18: Multiple Block Read - //const fn read_multiple_blocks(addr: u32) -> Cmd { - // Cmd::new(18, addr, Response::Short) - //} - - /// CMD24: Block Write - const fn write_single_block(addr: u32) -> Cmd { - Cmd::new(24, addr, Response::Short) - } - - const fn app_op_cmd(arg: u32) -> Cmd { - Cmd::new(41, arg, Response::Short) - } - - const fn cmd51() -> Cmd { - Cmd::new(51, 0, Response::Short) - } - - /// App Command. Indicates that next command will be a app command - const fn app_cmd(rca: u32) -> Cmd { - Cmd::new(55, rca, Response::Short) - } -} - ////////////////////////////////////////////////////// trait SealedInstance { @@ -1465,7 +1815,7 @@ trait SealedInstance { /// SDMMC instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + 'static { +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static { /// Interrupt for this instance. type Interrupt: interrupt::typelevel::Interrupt; } @@ -1484,15 +1834,6 @@ pin_trait!(D7Pin, Instance); #[cfg(sdmmc_v1)] dma_trait!(SdmmcDma, Instance); -/// DMA instance trait. -/// -/// This is only implemented for `NoDma`, since SDMMCv2 has DMA built-in, instead of -/// using ST's system-wide DMA peripheral. -#[cfg(sdmmc_v2)] -pub trait SdmmcDma {} -#[cfg(sdmmc_v2)] -impl SdmmcDma for NoDma {} - foreach_peripheral!( (sdmmc, $inst:ident) => { impl SealedInstance for peripherals::$inst { @@ -1511,3 +1852,46 @@ foreach_peripheral!( } }; ); + +impl<'d, T: Instance> block_device_driver::BlockDevice<512> for Sdmmc<'d, T> { + type Error = Error; + type Align = aligned::A4; + + async fn read( + &mut self, + block_address: u32, + buf: &mut [aligned::Aligned], + ) -> Result<(), Self::Error> { + // TODO: I think block_address needs to be adjusted by the partition start offset + if buf.len() == 1 { + let block = unsafe { &mut *(&mut buf[0] as *mut _ as *mut crate::sdmmc::DataBlock) }; + self.read_block(block_address, block).await?; + } else { + let blocks: &mut [DataBlock] = + unsafe { core::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut DataBlock, buf.len()) }; + self.read_blocks(block_address, blocks).await?; + } + Ok(()) + } + + async fn write( + &mut self, + block_address: u32, + buf: &[aligned::Aligned], + ) -> Result<(), Self::Error> { + // TODO: I think block_address needs to be adjusted by the partition start offset + if buf.len() == 1 { + let block = unsafe { &*(&buf[0] as *const _ as *const crate::sdmmc::DataBlock) }; + self.write_block(block_address, block).await?; + } else { + let blocks: &[DataBlock] = + unsafe { core::slice::from_raw_parts(buf.as_ptr() as *const DataBlock, buf.len()) }; + self.write_blocks(block_address, blocks).await?; + } + Ok(()) + } + + async fn size(&mut self) -> Result { + Ok(self.card()?.size()) + } +} diff --git a/embassy-stm32/src/spdifrx/mod.rs b/embassy-stm32/src/spdifrx/mod.rs new file mode 100644 index 000000000..9c42217f0 --- /dev/null +++ b/embassy-stm32/src/spdifrx/mod.rs @@ -0,0 +1,333 @@ +//! S/PDIF receiver +#![macro_use] +#![cfg_attr(gpdma, allow(unused))] + +use core::marker::PhantomData; + +use embassy_sync::waitqueue::AtomicWaker; + +use crate::dma::ringbuffer::Error as RingbufferError; +pub use crate::dma::word; +#[cfg(not(gpdma))] +use crate::dma::ReadableRingBuffer; +use crate::dma::{Channel, TransferOptions}; +use crate::gpio::{AfType, AnyPin, Pull, SealedPin as _}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::spdifrx::Spdifrx as Regs; +use crate::rcc::{RccInfo, SealedRccPeripheral}; +use crate::{interrupt, peripherals, Peri}; + +/// Possible S/PDIF preamble types. +#[allow(dead_code)] +#[repr(u8)] +enum PreambleType { + Unused = 0x00, + /// The preamble changes to preamble “B” once every 192 frames to identify the start of the block structure used to + /// organize the channel status and user information. + B = 0x01, + /// The first sub-frame (left or “A” channel in stereophonic operation and primary channel in monophonic operation) + /// normally starts with preamble “M” + M = 0x02, + /// The second sub-frame (right or “B” channel in stereophonic operation and secondary channel in monophonic + /// operation) always starts with preamble “W”. + W = 0x03, +} + +macro_rules! new_spdifrx_pin { + ($name:ident, $af_type:expr) => {{ + let pin = $name; + let input_sel = pin.input_sel(); + pin.set_as_af(pin.af_num(), $af_type); + (Some(pin.into()), input_sel) + }}; +} + +macro_rules! impl_spdifrx_pin { + ($inst:ident, $pin:ident, $af:expr, $sel:expr) => { + impl crate::spdifrx::InPin for crate::peripherals::$pin { + fn af_num(&self) -> u8 { + $af + } + fn input_sel(&self) -> u8 { + $sel + } + } + }; +} + +/// Ring-buffered SPDIFRX driver. +/// +/// Data is read by DMAs and stored in a ring buffer. +#[cfg(not(gpdma))] +pub struct Spdifrx<'d, T: Instance> { + _peri: Peri<'d, T>, + spdifrx_in: Option>, + data_ring_buffer: ReadableRingBuffer<'d, u32>, +} + +/// Gives the address of the data register. +fn dr_address(r: Regs) -> *mut u32 { + #[cfg(spdifrx_v1)] + let address = r.dr().as_ptr() as _; + #[cfg(spdifrx_h7)] + let address = r.fmt0_dr().as_ptr() as _; // All fmtx_dr() implementations have the same address. + + return address; +} + +/// Gives the address of the channel status register. +#[allow(unused)] +fn csr_address(r: Regs) -> *mut u32 { + r.csr().as_ptr() as _ +} + +/// Select the channel for capturing control information. +pub enum ControlChannelSelection { + /// Capture control info from channel A. + A, + /// Capture control info from channel B. + B, +} + +/// Configuration options for the SPDIFRX driver. +pub struct Config { + /// Select the channel for capturing control information. + pub control_channel_selection: ControlChannelSelection, +} + +/// S/PDIF errors. +#[derive(Debug)] +pub enum Error { + /// DMA overrun error. + RingbufferError(RingbufferError), + /// Left/right channel synchronization error. + ChannelSyncError, +} + +impl From for Error { + fn from(error: RingbufferError) -> Self { + Self::RingbufferError(error) + } +} + +impl Default for Config { + fn default() -> Self { + Self { + control_channel_selection: ControlChannelSelection::A, + } + } +} + +#[cfg(not(gpdma))] +impl<'d, T: Instance> Spdifrx<'d, T> { + fn dma_opts() -> TransferOptions { + TransferOptions { + half_transfer_ir: true, + // new_write() and new_read() always use circular mode + ..Default::default() + } + } + + /// Create a new `Spdifrx` instance. + pub fn new( + peri: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + config: Config, + spdifrx_in: Peri<'d, impl InPin>, + data_dma: Peri<'d, impl Channel + Dma>, + data_dma_buf: &'d mut [u32], + ) -> Self { + let (spdifrx_in, input_sel) = new_spdifrx_pin!(spdifrx_in, AfType::input(Pull::None)); + Self::setup(config, input_sel); + + let regs = T::info().regs; + let dr_request = data_dma.request(); + let dr_ring_buffer = + unsafe { ReadableRingBuffer::new(data_dma, dr_request, dr_address(regs), data_dma_buf, Self::dma_opts()) }; + + Self { + _peri: peri, + spdifrx_in, + data_ring_buffer: dr_ring_buffer, + } + } + + fn setup(config: Config, input_sel: u8) { + T::info().rcc.enable_and_reset(); + T::GlobalInterrupt::unpend(); + unsafe { T::GlobalInterrupt::enable() }; + + let regs = T::info().regs; + + regs.imr().write(|imr| { + imr.set_ifeie(true); // Enables interrupts for TERR, SERR, FERR. + imr.set_syncdie(true); // Enables SYNCD interrupt. + }); + + regs.cr().write(|cr| { + cr.set_spdifen(0x00); // Disable SPDIF receiver synchronization. + cr.set_rxdmaen(true); // Use RX DMA for data. Enabled on `read`. + cr.set_cbdmaen(false); // Do not capture channel info. + cr.set_rxsteo(true); // Operate in stereo mode. + cr.set_drfmt(0x01); // Data is left-aligned (MSB). + + // Disable all status fields in the data register. + // Status can be obtained directly with the status register DMA. + cr.set_pmsk(false); // Write parity bit to the data register. FIXME: Add parity check. + cr.set_vmsk(false); // Write validity to the data register. + cr.set_cumsk(true); // Do not write C and U bits to the data register. + cr.set_ptmsk(false); // Write preamble bits to the data register. + + cr.set_chsel(match config.control_channel_selection { + ControlChannelSelection::A => false, + ControlChannelSelection::B => true, + }); // Select channel status source. + + cr.set_nbtr(0x02); // 16 attempts are allowed. + cr.set_wfa(true); // Wait for activity before going to synchronization phase. + cr.set_insel(input_sel); // Input pin selection. + + #[cfg(stm32h7)] + cr.set_cksen(true); // Generate a symbol clock. + + #[cfg(stm32h7)] + cr.set_cksbkpen(true); // Generate a backup symbol clock. + }); + } + + /// Start the SPDIFRX driver. + pub fn start(&mut self) { + self.data_ring_buffer.start(); + + T::info().regs.cr().modify(|cr| { + cr.set_spdifen(0x03); // Enable S/PDIF receiver. + }); + } + + /// Read from the SPDIFRX data ring buffer. + /// + /// SPDIFRX is always receiving data in the background. This function pops already-received + /// data from the buffer. + /// + /// If there's less than `data.len()` data in the buffer, this waits until there is. + pub async fn read(&mut self, data: &mut [u32]) -> Result<(), Error> { + self.data_ring_buffer.read_exact(data).await?; + + let first_preamble = (data[0] >> 4) & 0b11_u32; + if (first_preamble as u8) == (PreambleType::W as u8) { + trace!("S/PDIF left/right mismatch"); + + // Resynchronize until the first sample is for the left channel. + self.data_ring_buffer.clear(); + return Err(Error::ChannelSyncError); + }; + + for sample in data.as_mut() { + if (*sample & (0x0002_u32)) != 0 { + // Discard invalid samples, setting them to mute level. + *sample = 0; + } else { + // Discard status information in the lowest byte. + *sample &= 0xFFFFFF00; + } + } + + Ok(()) + } +} + +#[cfg(not(gpdma))] +impl<'d, T: Instance> Drop for Spdifrx<'d, T> { + fn drop(&mut self) { + T::info().regs.cr().modify(|cr| cr.set_spdifen(0x00)); + self.spdifrx_in.as_ref().map(|x| x.set_as_disconnected()); + } +} + +struct State { + #[allow(unused)] + waker: AtomicWaker, +} + +impl State { + const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } +} + +struct Info { + regs: crate::pac::spdifrx::Spdifrx, + rcc: RccInfo, +} + +peri_trait!( + irqs: [GlobalInterrupt], +); + +/// SPIDFRX pin trait +pub trait InPin: crate::gpio::Pin { + /// Get the GPIO AF number needed to use this pin. + fn af_num(&self) -> u8; + /// Get the SPIDFRX INSEL number needed to use this pin. + fn input_sel(&self) -> u8; +} + +dma_trait!(Dma, Instance); + +/// Global interrupt handler. +pub struct GlobalInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for GlobalInterruptHandler { + unsafe fn on_interrupt() { + T::state().waker.wake(); + + let regs = T::info().regs; + let sr = regs.sr().read(); + + if sr.serr() || sr.terr() || sr.ferr() { + trace!("SPDIFRX error, resync"); + + // Clear errors by disabling SPDIFRX, then reenable. + regs.cr().modify(|cr| cr.set_spdifen(0x00)); + regs.cr().modify(|cr| cr.set_spdifen(0x03)); + } else if sr.syncd() { + // Synchronization was successful. + trace!("SPDIFRX sync success"); + } + + // Clear interrupt flags. + regs.ifcr().write(|ifcr| { + ifcr.set_perrcf(true); // Clears parity error flag. + ifcr.set_ovrcf(true); // Clears overrun error flag. + ifcr.set_sbdcf(true); // Clears synchronization block detected flag. + ifcr.set_syncdcf(true); // Clears SYNCD from SR (was read above). + }); + } +} + +foreach_peripheral!( + (spdifrx, $inst:ident) => { + #[allow(private_interfaces)] + impl SealedInstance for peripherals::$inst { + fn info() -> &'static Info { + static INFO: Info = Info{ + regs: crate::pac::$inst, + rcc: crate::peripherals::$inst::RCC_INFO, + }; + &INFO + } + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::$inst { + type GlobalInterrupt = crate::_generated::peripheral_interrupts::$inst::GLOBAL; + } + }; +); diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 20718147a..9e2ba093a 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -6,7 +6,6 @@ use core::ptr; use embassy_embedded_hal::SetConfig; use embassy_futures::join::join; -use embassy_hal_internal::PeripheralRef; pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; use crate::dma::{word, ChannelAndRequest}; @@ -15,7 +14,7 @@ use crate::mode::{Async, Blocking, Mode as PeriMode}; use crate::pac::spi::{regs, vals, Spi as Regs}; use crate::rcc::{RccInfo, SealedRccPeripheral}; use crate::time::Hertz; -use crate::Peripheral; +use crate::Peri; /// SPI error. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -31,6 +30,21 @@ pub enum Error { Overrun, } +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let message = match self { + Self::Framing => "Invalid Framing", + Self::Crc => "Hardware CRC Check Failed", + Self::ModeFault => "Mode Fault", + Self::Overrun => "Buffer Overrun", + }; + + write!(f, "{}", message) + } +} + +impl core::error::Error for Error {} + /// SPI bit order #[derive(Copy, Clone)] pub enum BitOrder { @@ -55,6 +69,9 @@ pub struct Config { /// There are some ICs that require a pull-up on the MISO pin for some applications. /// If you are unsure, you probably don't need this. pub miso_pull: Pull, + /// signal rise/fall speed (slew rate) - defaults to `Medium`. + /// Increase for high SPI speeds. Change to `Low` to reduce ringing. + pub rise_fall_speed: Speed, } impl Default for Config { @@ -64,6 +81,7 @@ impl Default for Config { bit_order: BitOrder::MsbFirst, frequency: Hertz(1_000_000), miso_pull: Pull::None, + rise_fall_speed: Speed::VeryHigh, } } } @@ -71,15 +89,15 @@ impl Default for Config { impl Config { fn raw_phase(&self) -> vals::Cpha { match self.mode.phase { - Phase::CaptureOnSecondTransition => vals::Cpha::SECONDEDGE, - Phase::CaptureOnFirstTransition => vals::Cpha::FIRSTEDGE, + Phase::CaptureOnSecondTransition => vals::Cpha::SECOND_EDGE, + Phase::CaptureOnFirstTransition => vals::Cpha::FIRST_EDGE, } } fn raw_polarity(&self) -> vals::Cpol { match self.mode.polarity { - Polarity::IdleHigh => vals::Cpol::IDLEHIGH, - Polarity::IdleLow => vals::Cpol::IDLELOW, + Polarity::IdleHigh => vals::Cpol::IDLE_HIGH, + Polarity::IdleLow => vals::Cpol::IDLE_LOW, } } @@ -92,14 +110,14 @@ impl Config { #[cfg(gpio_v1)] fn sck_af(&self) -> AfType { - AfType::output(OutputType::PushPull, Speed::VeryHigh) + AfType::output(OutputType::PushPull, self.rise_fall_speed) } #[cfg(gpio_v2)] fn sck_af(&self) -> AfType { AfType::output_pull( OutputType::PushPull, - Speed::VeryHigh, + self.rise_fall_speed, match self.mode.polarity { Polarity::IdleLow => Pull::Down, Polarity::IdleHigh => Pull::Up, @@ -111,21 +129,22 @@ impl Config { pub struct Spi<'d, M: PeriMode> { pub(crate) info: &'static Info, kernel_clock: Hertz, - sck: Option>, - mosi: Option>, - miso: Option>, + sck: Option>, + mosi: Option>, + miso: Option>, tx_dma: Option>, rx_dma: Option>, _phantom: PhantomData, current_word_size: word_impl::Config, + rise_fall_speed: Speed, } impl<'d, M: PeriMode> Spi<'d, M> { fn new_inner( - _peri: impl Peripheral

+ 'd, - sck: Option>, - mosi: Option>, - miso: Option>, + _peri: Peri<'d, T>, + sck: Option>, + mosi: Option>, + miso: Option>, tx_dma: Option>, rx_dma: Option>, config: Config, @@ -140,6 +159,7 @@ impl<'d, M: PeriMode> Spi<'d, M> { rx_dma, current_word_size: ::CONFIG, _phantom: PhantomData, + rise_fall_speed: config.rise_fall_speed, }; this.enable_and_init(config); this @@ -174,7 +194,7 @@ impl<'d, M: PeriMode> Spi<'d, M> { // we're doing "fake rxonly", by actually writing one // byte to TXDR for each byte we want to receive. if we // set OUTPUTDISABLED here, this hangs. - w.set_rxonly(vals::Rxonly::FULLDUPLEX); + w.set_rxonly(vals::Rxonly::FULL_DUPLEX); w.set_dff(::CONFIG) }); } @@ -211,18 +231,18 @@ impl<'d, M: PeriMode> Spi<'d, M> { w.set_lsbfirst(lsbfirst); w.set_ssm(true); w.set_master(vals::Master::MASTER); - w.set_comm(vals::Comm::FULLDUPLEX); + w.set_comm(vals::Comm::FULL_DUPLEX); w.set_ssom(vals::Ssom::ASSERTED); w.set_midi(0); w.set_mssi(0); w.set_afcntr(true); - w.set_ssiop(vals::Ssiop::ACTIVEHIGH); + w.set_ssiop(vals::Ssiop::ACTIVE_HIGH); }); regs.cfg1().modify(|w| { w.set_crcen(false); w.set_mbr(br); w.set_dsize(::CONFIG); - w.set_fthlv(vals::Fthlv::ONEFRAME); + w.set_fthlv(vals::Fthlv::ONE_FRAME); }); regs.cr2().modify(|w| { w.set_tsize(0); @@ -243,6 +263,17 @@ impl<'d, M: PeriMode> Spi<'d, M> { let br = compute_baud_rate(self.kernel_clock, config.frequency); + #[cfg(gpio_v2)] + { + self.rise_fall_speed = config.rise_fall_speed; + if let Some(sck) = self.sck.as_ref() { + sck.set_speed(config.rise_fall_speed); + } + if let Some(mosi) = self.mosi.as_ref() { + mosi.set_speed(config.rise_fall_speed); + } + } + #[cfg(any(spi_v1, spi_f1, spi_v2))] self.info.regs.cr1().modify(|w| { w.set_cpha(cpha); @@ -253,6 +284,10 @@ impl<'d, M: PeriMode> Spi<'d, M> { #[cfg(any(spi_v3, spi_v4, spi_v5))] { + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + self.info.regs.cfg2().modify(|w| { w.set_cpha(cpha); w.set_cpol(cpol); @@ -261,6 +296,10 @@ impl<'d, M: PeriMode> Spi<'d, M> { self.info.regs.cfg1().modify(|w| { w.set_mbr(br); }); + + self.info.regs.cr1().modify(|w| { + w.set_spe(true); + }); } Ok(()) } @@ -274,12 +313,12 @@ impl<'d, M: PeriMode> Spi<'d, M> { #[cfg(any(spi_v3, spi_v4, spi_v5))] let cfg1 = self.info.regs.cfg1().read(); - let polarity = if cfg.cpol() == vals::Cpol::IDLELOW { + let polarity = if cfg.cpol() == vals::Cpol::IDLE_LOW { Polarity::IdleLow } else { Polarity::IdleHigh }; - let phase = if cfg.cpha() == vals::Cpha::FIRSTEDGE { + let phase = if cfg.cpha() == vals::Cpha::FIRST_EDGE { Phase::CaptureOnFirstTransition } else { Phase::CaptureOnSecondTransition @@ -308,54 +347,32 @@ impl<'d, M: PeriMode> Spi<'d, M> { bit_order, frequency, miso_pull, + rise_fall_speed: self.rise_fall_speed, } } - fn set_word_size(&mut self, word_size: word_impl::Config) { + pub(crate) fn set_word_size(&mut self, word_size: word_impl::Config) { if self.current_word_size == word_size { return; } + self.info.regs.cr1().modify(|w| { + w.set_spe(false); + }); + #[cfg(any(spi_v1, spi_f1))] - { - self.info.regs.cr1().modify(|reg| { - reg.set_spe(false); - reg.set_dff(word_size) - }); - self.info.regs.cr1().modify(|reg| { - reg.set_spe(true); - }); - } + self.info.regs.cr1().modify(|reg| { + reg.set_dff(word_size); + }); #[cfg(spi_v2)] - { - self.info.regs.cr1().modify(|w| { - w.set_spe(false); - }); - self.info.regs.cr2().modify(|w| { - w.set_frxth(word_size.1); - w.set_ds(word_size.0); - }); - self.info.regs.cr1().modify(|w| { - w.set_spe(true); - }); - } + self.info.regs.cr2().modify(|w| { + w.set_frxth(word_size.1); + w.set_ds(word_size.0); + }); #[cfg(any(spi_v3, spi_v4, spi_v5))] - { - self.info.regs.cr1().modify(|w| { - w.set_csusp(true); - }); - while self.info.regs.sr().read().eot() {} - self.info.regs.cr1().modify(|w| { - w.set_spe(false); - }); - self.info.regs.cfg1().modify(|w| { - w.set_dsize(word_size); - }); - self.info.regs.cr1().modify(|w| { - w.set_csusp(false); - w.set_spe(true); - }); - } + self.info.regs.cfg1().modify(|w| { + w.set_dsize(word_size); + }); self.current_word_size = word_size; } @@ -365,9 +382,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter() { // this cannot use `transfer_word` because on SPIv2 and higher, // the SPI RX state machine hangs if no physical pin is connected to the SCK AF. @@ -402,9 +419,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(self.info.regs, W::default())?; } @@ -418,9 +435,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(self.info.regs, *word)?; } @@ -437,9 +454,9 @@ impl<'d, M: PeriMode> Spi<'d, M> { // needed in v3+ to avoid overrun causing the SPI RX state machine to get stuck...? #[cfg(any(spi_v3, spi_v4, spi_v5))] self.info.regs.cr1().modify(|w| w.set_spe(false)); + self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(self.info.regs); - self.set_word_size(W::CONFIG); let len = read.len().max(write.len()); for i in 0..len { let wb = write.get(i).copied().unwrap_or_default(); @@ -455,16 +472,16 @@ impl<'d, M: PeriMode> Spi<'d, M> { impl<'d> Spi<'d, Blocking> { /// Create a new blocking SPI driver. pub fn new_blocking( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - mosi: impl Peripheral

> + 'd, - miso: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + mosi: Peri<'d, impl MosiPin>, + miso: Peri<'d, impl MisoPin>, config: Config, ) -> Self { Self::new_inner( peri, new_pin!(sck, config.sck_af()), - new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(mosi, AfType::output(OutputType::PushPull, config.rise_fall_speed)), new_pin!(miso, AfType::input(config.miso_pull)), None, None, @@ -474,9 +491,9 @@ impl<'d> Spi<'d, Blocking> { /// Create a new blocking SPI driver, in RX-only mode (only MISO pin, no MOSI). pub fn new_blocking_rxonly( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - miso: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + miso: Peri<'d, impl MisoPin>, config: Config, ) -> Self { Self::new_inner( @@ -492,15 +509,15 @@ impl<'d> Spi<'d, Blocking> { /// Create a new blocking SPI driver, in TX-only mode (only MOSI pin, no MISO). pub fn new_blocking_txonly( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - mosi: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + mosi: Peri<'d, impl MosiPin>, config: Config, ) -> Self { Self::new_inner( peri, new_pin!(sck, config.sck_af()), - new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(mosi, AfType::output(OutputType::PushPull, config.rise_fall_speed)), None, None, None, @@ -512,14 +529,14 @@ impl<'d> Spi<'d, Blocking> { /// /// This can be useful for bit-banging non-SPI protocols. pub fn new_blocking_txonly_nosck( - peri: impl Peripheral

+ 'd, - mosi: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + mosi: Peri<'d, impl MosiPin>, config: Config, ) -> Self { Self::new_inner( peri, None, - new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(mosi, AfType::output(OutputType::PushPull, config.rise_fall_speed)), None, None, None, @@ -531,18 +548,18 @@ impl<'d> Spi<'d, Blocking> { impl<'d> Spi<'d, Async> { /// Create a new SPI driver. pub fn new( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - mosi: impl Peripheral

> + 'd, - miso: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + mosi: Peri<'d, impl MosiPin>, + miso: Peri<'d, impl MisoPin>, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, config: Config, ) -> Self { Self::new_inner( peri, new_pin!(sck, config.sck_af()), - new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(mosi, AfType::output(OutputType::PushPull, config.rise_fall_speed)), new_pin!(miso, AfType::input(config.miso_pull)), new_dma!(tx_dma), new_dma!(rx_dma), @@ -552,11 +569,11 @@ impl<'d> Spi<'d, Async> { /// Create a new SPI driver, in RX-only mode (only MISO pin, no MOSI). pub fn new_rxonly( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - miso: impl Peripheral

> + 'd, - #[cfg(any(spi_v1, spi_f1, spi_v2))] tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + miso: Peri<'d, impl MisoPin>, + #[cfg(any(spi_v1, spi_f1, spi_v2))] tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, config: Config, ) -> Self { Self::new_inner( @@ -575,16 +592,16 @@ impl<'d> Spi<'d, Async> { /// Create a new SPI driver, in TX-only mode (only MOSI pin, no MISO). pub fn new_txonly( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - mosi: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + sck: Peri<'d, impl SckPin>, + mosi: Peri<'d, impl MosiPin>, + tx_dma: Peri<'d, impl TxDma>, config: Config, ) -> Self { Self::new_inner( peri, new_pin!(sck, config.sck_af()), - new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(mosi, AfType::output(OutputType::PushPull, config.rise_fall_speed)), None, new_dma!(tx_dma), None, @@ -596,15 +613,15 @@ impl<'d> Spi<'d, Async> { /// /// This can be useful for bit-banging non-SPI protocols. pub fn new_txonly_nosck( - peri: impl Peripheral

+ 'd, - mosi: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + mosi: Peri<'d, impl MosiPin>, + tx_dma: Peri<'d, impl TxDma>, config: Config, ) -> Self { Self::new_inner( peri, None, - new_pin!(mosi, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(mosi, AfType::output(OutputType::PushPull, config.rise_fall_speed)), None, new_dma!(tx_dma), None, @@ -615,9 +632,9 @@ impl<'d> Spi<'d, Async> { #[cfg(stm32wl)] /// Useful for on chip peripherals like SUBGHZ which are hardwired. pub fn new_subghz( - peri: impl Peripheral

+ 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, ) -> Self { // see RM0453 rev 1 section 7.2.13 page 291 // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two. @@ -634,7 +651,7 @@ impl<'d> Spi<'d, Async> { #[allow(dead_code)] pub(crate) fn new_internal( - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, tx_dma: Option>, rx_dma: Option>, config: Config, @@ -648,10 +665,10 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); let tx_dst = self.info.regs.tx_ptr(); let tx_f = unsafe { self.tx_dma.as_mut().unwrap().write(data, tx_dst, Default::default()) }; @@ -685,6 +702,8 @@ impl<'d> Spi<'d, Async> { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + let comm = regs.cfg2().modify(|w| { let prev = w.comm(); w.set_comm(vals::Comm::RECEIVER); @@ -696,8 +715,8 @@ impl<'d> Spi<'d, Async> { w.i2smod().then(|| { let prev = w.i2scfg(); w.set_i2scfg(match prev { - vals::I2scfg::SLAVERX | vals::I2scfg::SLAVEFULLDUPLEX => vals::I2scfg::SLAVERX, - vals::I2scfg::MASTERRX | vals::I2scfg::MASTERFULLDUPLEX => vals::I2scfg::MASTERRX, + vals::I2scfg::SLAVE_RX | vals::I2scfg::SLAVE_FULL_DUPLEX => vals::I2scfg::SLAVE_RX, + vals::I2scfg::MASTER_RX | vals::I2scfg::MASTER_FULL_DUPLEX => vals::I2scfg::MASTER_RX, _ => panic!("unsupported configuration"), }); prev @@ -707,7 +726,6 @@ impl<'d> Spi<'d, Async> { let rx_src = regs.rx_ptr(); for mut chunk in data.chunks_mut(u16::max_value().into()) { - self.set_word_size(W::CONFIG); set_rxdmaen(regs, true); let tsize = chunk.len(); @@ -765,12 +783,12 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); - self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + // SPIv3 clears rxfifo on SPE=0 #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(self.info.regs); @@ -783,7 +801,7 @@ impl<'d> Spi<'d, Async> { let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read(rx_src, data, Default::default()) }; let tx_dst = self.info.regs.tx_ptr(); - let clock_byte = 0x00u8; + let clock_byte = W::default(); let tx_f = unsafe { self.tx_dma .as_mut() @@ -813,11 +831,12 @@ impl<'d> Spi<'d, Async> { return Ok(()); } - self.set_word_size(W::CONFIG); self.info.regs.cr1().modify(|w| { w.set_spe(false); }); + self.set_word_size(W::CONFIG); + // SPIv3 clears rxfifo on SPE=0 #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(self.info.regs); @@ -827,7 +846,7 @@ impl<'d> Spi<'d, Async> { let rx_src = self.info.regs.rx_ptr(); let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read_raw(rx_src, read, Default::default()) }; - let tx_dst = self.info.regs.tx_ptr(); + let tx_dst: *mut W = self.info.regs.tx_ptr(); let tx_f = unsafe { self.tx_dma .as_mut() @@ -915,7 +934,7 @@ fn compute_frequency(kernel_clock: Hertz, br: Br) -> Hertz { kernel_clock / div } -trait RegsExt { +pub(crate) trait RegsExt { fn tx_ptr(&self) -> *mut W; fn rx_ptr(&self) -> *mut W; } @@ -1003,7 +1022,7 @@ fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> { } } -fn flush_rx_fifo(regs: Regs) { +pub(crate) fn flush_rx_fifo(regs: Regs) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] while regs.sr().read().rxne() { #[cfg(not(spi_v2))] @@ -1017,7 +1036,7 @@ fn flush_rx_fifo(regs: Regs) { } } -fn set_txdmaen(regs: Regs, val: bool) { +pub(crate) fn set_txdmaen(regs: Regs, val: bool) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] regs.cr2().modify(|reg| { reg.set_txdmaen(val); @@ -1028,7 +1047,7 @@ fn set_txdmaen(regs: Regs, val: bool) { }); } -fn set_rxdmaen(regs: Regs, val: bool) { +pub(crate) fn set_rxdmaen(regs: Regs, val: bool) { #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] regs.cr2().modify(|reg| { reg.set_rxdmaen(val); @@ -1189,13 +1208,13 @@ impl<'d, W: Word> embedded_hal_async::spi::SpiBus for Spi<'d, Async> { } } -trait SealedWord { +pub(crate) trait SealedWord { const CONFIG: word_impl::Config; } /// Word sizes usable for SPI. #[allow(private_bounds)] -pub trait Word: word::Word + SealedWord {} +pub trait Word: word::Word + SealedWord + Default {} macro_rules! impl_word { ($T:ty, $config:expr) => { diff --git a/embassy-stm32/src/time.rs b/embassy-stm32/src/time.rs index 802ff41ce..532877f70 100644 --- a/embassy-stm32/src/time.rs +++ b/embassy-stm32/src/time.rs @@ -1,12 +1,25 @@ //! Time units +use core::fmt::Display; use core::ops::{Div, Mul}; /// Hertz #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Hertz(pub u32); +impl Display for Hertz { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} Hz", self.0) + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Hertz { + fn format(&self, f: defmt::Formatter) { + defmt::write!(f, "{=u32} Hz", self.0) + } +} + impl Hertz { /// Create a `Hertz` from the given hertz. pub const fn hz(hertz: u32) -> Self { diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index f8041bf1e..7db74bdf6 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -1,13 +1,13 @@ #![allow(non_snake_case)] -use core::cell::Cell; -use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; -use core::{mem, ptr}; +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use critical_section::CriticalSection; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::Mutex; -use embassy_time_driver::{AlarmHandle, Driver, TICK_HZ}; +use embassy_time_driver::{Driver, TICK_HZ}; +use embassy_time_queue_utils::Queue; use stm32_metapac::timer::{regs, TimGp16}; use crate::interrupt::typelevel::Interrupt; @@ -24,18 +24,6 @@ use crate::{interrupt, peripherals}; // 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. (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_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; @@ -67,14 +55,6 @@ type T = peripherals::TIM23; type T = peripherals::TIM24; foreach_interrupt! { - (TIM1, timer, $block:ident, CC, $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")] @@ -123,14 +103,6 @@ foreach_interrupt! { DRIVER.on_interrupt() } }; - (TIM8, timer, $block:ident, CC, $irq:ident) => { - #[cfg(time_driver_tim8)] - #[cfg(feature = "rt")] - #[interrupt] - fn $irq() { - DRIVER.on_interrupt() - } - }; (TIM9, timer, $block:ident, CC, $irq:ident) => { #[cfg(time_driver_tim9)] #[cfg(feature = "rt")] @@ -163,14 +135,6 @@ foreach_interrupt! { 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, CC, $irq:ident) => { #[cfg(time_driver_tim21)] #[cfg(feature = "rt")] @@ -232,11 +196,6 @@ fn calc_now(period: u32, counter: u16) -> u64 { struct AlarmState { timestamp: Cell, - - // This is really a Option<(fn(*mut ()), *mut ())> - // but fn pointers aren't allowed in const yet - callback: Cell<*const ()>, - ctx: Cell<*mut ()>, } unsafe impl Send for AlarmState {} @@ -245,8 +204,6 @@ impl AlarmState { const fn new() -> Self { Self { timestamp: Cell::new(u64::MAX), - callback: Cell::new(ptr::null()), - ctx: Cell::new(ptr::null_mut()), } } } @@ -254,22 +211,18 @@ impl AlarmState { pub(crate) struct RtcDriver { /// Number of 2^15 periods elapsed since boot. period: AtomicU32, - alarm_count: AtomicU8, - /// Timestamp at which to fire alarm. u64::MAX if no alarm is scheduled. - alarms: Mutex, + alarm: Mutex, #[cfg(feature = "low-power")] rtc: Mutex>>, + queue: Mutex>, } -#[allow(clippy::declare_interior_mutable_const)] -const ALARM_STATE_NEW: AlarmState = AlarmState::new(); - embassy_time_driver::time_driver_impl!(static DRIVER: RtcDriver = RtcDriver { period: AtomicU32::new(0), - alarm_count: AtomicU8::new(0), - alarms: Mutex::const_new(CriticalSectionRawMutex::new(), [ALARM_STATE_NEW; ALARM_COUNT]), + alarm: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState::new()), #[cfg(feature = "low-power")] rtc: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), + queue: Mutex::new(RefCell::new(Queue::new())) }); impl RtcDriver { @@ -293,9 +246,9 @@ impl RtcDriver { r.arr().write(|w| w.set_arr(u16::MAX)); // Set URS, generate update and clear URS - r.cr1().modify(|w| w.set_urs(vals::Urs::COUNTERONLY)); + r.cr1().modify(|w| w.set_urs(vals::Urs::COUNTER_ONLY)); r.egr().write(|w| w.set_ug(true)); - r.cr1().modify(|w| w.set_urs(vals::Urs::ANYEVENT)); + r.cr1().modify(|w| w.set_urs(vals::Urs::ANY_EVENT)); // Mid-way point r.ccr(0).write(|w| w.set_ccr(0x8000)); @@ -315,7 +268,6 @@ impl RtcDriver { fn on_interrupt(&self) { let r = regs_gp16(); - // XXX: reduce the size of this critical section ? critical_section::with(|cs| { let sr = r.sr().read(); let dier = r.dier().read(); @@ -335,10 +287,9 @@ impl RtcDriver { self.next_period(); } - for n in 0..ALARM_COUNT { - if sr.ccif(n + 1) && dier.ccie(n + 1) { - self.trigger_alarm(n, cs); - } + let n = 0; + if sr.ccif(n + 1) && dier.ccie(n + 1) { + self.trigger_alarm(cs); } }) } @@ -353,36 +304,23 @@ impl RtcDriver { critical_section::with(move |cs| { r.dier().modify(move |w| { - for n in 0..ALARM_COUNT { - let alarm = &self.alarms.borrow(cs)[n]; - let at = alarm.timestamp.get(); + let n = 0; + let alarm = self.alarm.borrow(cs); + let at = alarm.timestamp.get(); - if at < t + 0xc000 { - // just enable it. `set_alarm` has already set the correct CCR val. - w.set_ccie(n + 1, true); - } + if at < t + 0xc000 { + // just enable it. `set_alarm` has already set the correct CCR val. + w.set_ccie(n + 1, true); } }) }) } - fn get_alarm<'a>(&'a self, cs: CriticalSection<'a>, alarm: AlarmHandle) -> &'a AlarmState { - // safety: we're allowed to assume the AlarmState is created by us, and - // we never create one that's out of bounds. - unsafe { self.alarms.borrow(cs).get_unchecked(alarm.id() as usize) } - } - - fn trigger_alarm(&self, n: usize, cs: CriticalSection) { - let alarm = &self.alarms.borrow(cs)[n]; - alarm.timestamp.set(u64::MAX); - - // Call after clearing alarm, so the callback can set another alarm. - - // safety: - // - we can ignore the possibility of `f` being unset (null) because of the safety contract of `allocate_alarm`. - // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; - f(alarm.ctx.get()); + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } } /* @@ -394,14 +332,7 @@ impl RtcDriver { fn time_until_next_alarm(&self, cs: CriticalSection) -> embassy_time::Duration { let now = self.now() + 32; - embassy_time::Duration::from_ticks( - self.alarms - .borrow(cs) - .iter() - .map(|alarm: &AlarmState| alarm.timestamp.get().saturating_sub(now)) - .min() - .unwrap_or(u64::MAX), - ) + embassy_time::Duration::from_ticks(self.alarm.borrow(cs).timestamp.get().saturating_sub(now)) } #[cfg(feature = "low-power")] @@ -436,12 +367,12 @@ impl RtcDriver { self.period.store(period, Ordering::SeqCst); regs_gp16().cnt().write(|w| w.set_cnt(cnt as u16)); - // Now, recompute all alarms - for i in 0..ALARM_COUNT { - let alarm_handle = unsafe { AlarmHandle::new(i as u8) }; - let alarm = self.get_alarm(cs, alarm_handle); + // Now, recompute alarm + let alarm = self.alarm.borrow(cs); - self.set_alarm(alarm_handle, alarm.timestamp.get()); + if !self.set_alarm(cs, alarm.timestamp.get()) { + // If the alarm timestamp has passed, we need to trigger it + self.trigger_alarm(cs); } } @@ -513,6 +444,49 @@ impl RtcDriver { regs_gp16().cr1().modify(|w| w.set_cen(true)); }) } + + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let r = regs_gp16(); + + let n = 0; + self.alarm.borrow(cs).timestamp.set(timestamp); + + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + r.dier().modify(|w| w.set_ccie(n + 1, false)); + + self.alarm.borrow(cs).timestamp.set(u64::MAX); + + return false; + } + + // Write the CCR value regardless of whether we're going to enable it now or not. + // This way, when we enable it later, the right value is already set. + r.ccr(n + 1).write(|w| w.set_ccr(timestamp as u16)); + + // Enable it if it'll happen soon. Otherwise, `next_period` will enable it. + let diff = timestamp - t; + r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000)); + + // Reevaluate if the alarm timestamp is still in the future + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed since we set it, we have a race condition and + // the alarm may or may not have fired. + // Disarm the alarm and return `false` to indicate that. + // It is the caller's responsibility to handle this ambiguity. + r.dier().modify(|w| w.set_ccie(n + 1, false)); + + self.alarm.borrow(cs).timestamp.set(u64::MAX); + + return false; + } + + // We're confident the alarm will ring in the future. + true + } } impl Driver for RtcDriver { @@ -525,70 +499,16 @@ impl Driver for RtcDriver { calc_now(period, counter) } - unsafe fn allocate_alarm(&self) -> Option { - 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 - } - }) - } - - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { critical_section::with(|cs| { - let alarm = self.get_alarm(cs, alarm); + let mut queue = self.queue.borrow(cs).borrow_mut(); - alarm.callback.set(callback as *const ()); - alarm.ctx.set(ctx); - }) - } - - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { - critical_section::with(|cs| { - let r = regs_gp16(); - - let n = alarm.id() as usize; - let alarm = self.get_alarm(cs, alarm); - alarm.timestamp.set(timestamp); - - let t = self.now(); - if timestamp <= t { - // If alarm timestamp has passed the alarm will not fire. - // Disarm the alarm and return `false` to indicate that. - r.dier().modify(|w| w.set_ccie(n + 1, false)); - - alarm.timestamp.set(u64::MAX); - - return false; + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } } - - // Write the CCR value regardless of whether we're going to enable it now or not. - // This way, when we enable it later, the right value is already set. - r.ccr(n + 1).write(|w| w.set_ccr(timestamp as u16)); - - // Enable it if it'll happen soon. Otherwise, `next_period` will enable it. - let diff = timestamp - t; - r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000)); - - // Reevaluate if the alarm timestamp is still in the future - let t = self.now(); - if timestamp <= t { - // If alarm timestamp has passed since we set it, we have a race condition and - // the alarm may or may not have fired. - // Disarm the alarm and return `false` to indicate that. - // It is the caller's responsibility to handle this ambiguity. - r.dier().modify(|w| w.set_ccie(n + 1, false)); - - alarm.timestamp.set(u64::MAX); - - return false; - } - - // We're confident the alarm will ring in the future. - true }) } } diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 46ccbf3df..8eec6c0c7 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -2,7 +2,6 @@ use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, PeripheralRef}; use stm32_metapac::timer::vals::Ckd; use super::low_level::{CountingMode, OutputPolarity, Timer}; @@ -14,13 +13,13 @@ use super::{ use crate::gpio::{AnyPin, OutputType}; use crate::time::Hertz; use crate::timer::low_level::OutputCompareMode; -use crate::Peripheral; +use crate::Peri; /// Complementary PWM pin wrapper. /// /// This wraps a pin to make it usable with PWM. pub struct ComplementaryPwmPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, + _pin: Peri<'d, AnyPin>, phantom: PhantomData<(T, C)>, } @@ -28,8 +27,7 @@ macro_rules! complementary_channel_impl { ($new_chx:ident, $channel:ident, $pin_trait:ident) => { impl<'d, T: AdvancedInstance4Channel> ComplementaryPwmPin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd, output_type: OutputType) -> Self { - into_ref!(pin); + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>, output_type: OutputType) -> Self { critical_section::with(|_| { pin.set_low(); pin.set_as_af( @@ -38,7 +36,7 @@ macro_rules! complementary_channel_impl { ); }); ComplementaryPwmPin { - _pin: pin.map_into(), + _pin: pin.into(), phantom: PhantomData, } } @@ -60,7 +58,7 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { /// Create a new complementary PWM driver. #[allow(clippy::too_many_arguments)] pub fn new( - tim: impl Peripheral

+ 'd, + tim: Peri<'d, T>, _ch1: Option>, _ch1n: Option>, _ch2: Option>, @@ -75,7 +73,7 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { Self::new_inner(tim, freq, counting_mode) } - fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { + fn new_inner(tim: Peri<'d, T>, freq: Hertz, counting_mode: CountingMode) -> Self { let mut this = Self { inner: Timer::new(tim) }; this.inner.set_counting_mode(counting_mode); @@ -116,7 +114,7 @@ impl<'d, T: AdvancedInstance4Channel> ComplementaryPwm<'d, T> { } else { 1u8 }; - self.inner.set_frequency(freq * multiplier); + self.inner.set_frequency_internal(freq * multiplier, 16); } /// Get max duty value. @@ -242,11 +240,11 @@ fn compute_dead_time_value(value: u16) -> (Ckd, u8) { let (these_bits, result) = if target < 128 { (target as u8, target) } else if target < 255 { - (64 + (target / 2) as u8, (target - target % 2)) + ((64 + (target / 2) as u8) | 128, (target - target % 2)) } else if target < 508 { - (32 + (target / 8) as u8, (target - target % 8)) + ((32 + (target / 8) as u8) | 192, (target - target % 8)) } else if target < 1008 { - (32 + (target / 16) as u8, (target - target % 16)) + ((32 + (target / 16) as u8) | 224, (target - target % 16)) } else { (u8::MAX, 1008) }; @@ -302,7 +300,7 @@ mod tests { TestRun { value: 400, ckd: Ckd::DIV1, - bits: 32 + (400u16 / 8) as u8, + bits: 210, }, TestRun { value: 600, diff --git a/embassy-stm32/src/timer/input_capture.rs b/embassy-stm32/src/timer/input_capture.rs index 341ac2c04..ec8b1ddf1 100644 --- a/embassy-stm32/src/timer/input_capture.rs +++ b/embassy-stm32/src/timer/input_capture.rs @@ -5,32 +5,22 @@ use core::marker::PhantomData; use core::pin::Pin; use core::task::{Context, Poll}; -use embassy_hal_internal::{into_ref, PeripheralRef}; - use super::low_level::{CountingMode, FilterValue, InputCaptureMode, InputTISelection, Timer}; use super::{ CaptureCompareInterruptHandler, Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, GeneralInstance4Channel, }; +pub use super::{Ch1, Ch2, Ch3, Ch4}; use crate::gpio::{AfType, AnyPin, Pull}; use crate::interrupt::typelevel::{Binding, Interrupt}; use crate::time::Hertz; -use crate::Peripheral; - -/// Channel 1 marker type. -pub enum Ch1 {} -/// Channel 2 marker type. -pub enum Ch2 {} -/// Channel 3 marker type. -pub enum Ch3 {} -/// Channel 4 marker type. -pub enum Ch4 {} +use crate::Peri; /// Capture pin wrapper. /// /// This wraps a pin to make it usable with capture. pub struct CapturePin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, + _pin: Peri<'d, AnyPin>, phantom: PhantomData<(T, C)>, } @@ -38,11 +28,10 @@ macro_rules! channel_impl { ($new_chx:ident, $channel:ident, $pin_trait:ident) => { impl<'d, T: GeneralInstance4Channel> CapturePin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " capture pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd, pull: Pull) -> Self { - into_ref!(pin); + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>, pull: Pull) -> Self { pin.set_as_af(pin.af_num(), AfType::input(pull)); CapturePin { - _pin: pin.map_into(), + _pin: pin.into(), phantom: PhantomData, } } @@ -63,7 +52,7 @@ pub struct InputCapture<'d, T: GeneralInstance4Channel> { impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { /// Create a new input capture driver. pub fn new( - tim: impl Peripheral

+ 'd, + tim: Peri<'d, T>, _ch1: Option>, _ch2: Option>, _ch3: Option>, @@ -75,7 +64,7 @@ impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { Self::new_inner(tim, freq, counting_mode) } - fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { + fn new_inner(tim: Peri<'d, T>, freq: Hertz, counting_mode: CountingMode) -> Self { let mut this = Self { inner: Timer::new(tim) }; this.inner.set_counting_mode(counting_mode); @@ -129,7 +118,7 @@ impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.5 // or ST RM0008 (STM32F103) chapter 15.3.5 Input capture mode self.inner.set_input_ti_selection(channel, tisel); - self.inner.set_input_capture_filter(channel, FilterValue::NOFILTER); + self.inner.set_input_capture_filter(channel, FilterValue::NO_FILTER); self.inner.set_input_capture_mode(channel, mode); self.inner.set_input_capture_prescaler(channel, 0); self.inner.enable_channel(channel, true); diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index e643722aa..dc8ceb725 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -6,7 +6,9 @@ //! //! The available functionality depends on the timer type. -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use core::mem::ManuallyDrop; + +use embassy_hal_internal::Peri; // Re-export useful enums pub use stm32_metapac::timer::vals::{FilterValue, Sms as SlaveMode, Ts as TriggerSource}; @@ -93,11 +95,11 @@ impl CountingMode { impl From for (vals::Cms, vals::Dir) { fn from(value: CountingMode) -> Self { match value { - CountingMode::EdgeAlignedUp => (vals::Cms::EDGEALIGNED, vals::Dir::UP), - CountingMode::EdgeAlignedDown => (vals::Cms::EDGEALIGNED, vals::Dir::DOWN), - CountingMode::CenterAlignedDownInterrupts => (vals::Cms::CENTERALIGNED1, vals::Dir::UP), - CountingMode::CenterAlignedUpInterrupts => (vals::Cms::CENTERALIGNED2, vals::Dir::UP), - CountingMode::CenterAlignedBothInterrupts => (vals::Cms::CENTERALIGNED3, vals::Dir::UP), + CountingMode::EdgeAlignedUp => (vals::Cms::EDGE_ALIGNED, vals::Dir::UP), + CountingMode::EdgeAlignedDown => (vals::Cms::EDGE_ALIGNED, vals::Dir::DOWN), + CountingMode::CenterAlignedDownInterrupts => (vals::Cms::CENTER_ALIGNED1, vals::Dir::UP), + CountingMode::CenterAlignedUpInterrupts => (vals::Cms::CENTER_ALIGNED2, vals::Dir::UP), + CountingMode::CenterAlignedBothInterrupts => (vals::Cms::CENTER_ALIGNED3, vals::Dir::UP), } } } @@ -105,11 +107,11 @@ impl From for (vals::Cms, vals::Dir) { impl From<(vals::Cms, vals::Dir)> for CountingMode { fn from(value: (vals::Cms, vals::Dir)) -> Self { match value { - (vals::Cms::EDGEALIGNED, vals::Dir::UP) => CountingMode::EdgeAlignedUp, - (vals::Cms::EDGEALIGNED, vals::Dir::DOWN) => CountingMode::EdgeAlignedDown, - (vals::Cms::CENTERALIGNED1, _) => CountingMode::CenterAlignedDownInterrupts, - (vals::Cms::CENTERALIGNED2, _) => CountingMode::CenterAlignedUpInterrupts, - (vals::Cms::CENTERALIGNED3, _) => CountingMode::CenterAlignedBothInterrupts, + (vals::Cms::EDGE_ALIGNED, vals::Dir::UP) => CountingMode::EdgeAlignedUp, + (vals::Cms::EDGE_ALIGNED, vals::Dir::DOWN) => CountingMode::EdgeAlignedDown, + (vals::Cms::CENTER_ALIGNED1, _) => CountingMode::CenterAlignedDownInterrupts, + (vals::Cms::CENTER_ALIGNED2, _) => CountingMode::CenterAlignedUpInterrupts, + (vals::Cms::CENTER_ALIGNED3, _) => CountingMode::CenterAlignedBothInterrupts, } } } @@ -148,13 +150,13 @@ impl From for stm32_metapac::timer::vals::Ocm { fn from(mode: OutputCompareMode) -> Self { match mode { OutputCompareMode::Frozen => stm32_metapac::timer::vals::Ocm::FROZEN, - OutputCompareMode::ActiveOnMatch => stm32_metapac::timer::vals::Ocm::ACTIVEONMATCH, - OutputCompareMode::InactiveOnMatch => stm32_metapac::timer::vals::Ocm::INACTIVEONMATCH, + OutputCompareMode::ActiveOnMatch => stm32_metapac::timer::vals::Ocm::ACTIVE_ON_MATCH, + OutputCompareMode::InactiveOnMatch => stm32_metapac::timer::vals::Ocm::INACTIVE_ON_MATCH, OutputCompareMode::Toggle => stm32_metapac::timer::vals::Ocm::TOGGLE, - OutputCompareMode::ForceInactive => stm32_metapac::timer::vals::Ocm::FORCEINACTIVE, - OutputCompareMode::ForceActive => stm32_metapac::timer::vals::Ocm::FORCEACTIVE, - OutputCompareMode::PwmMode1 => stm32_metapac::timer::vals::Ocm::PWMMODE1, - OutputCompareMode::PwmMode2 => stm32_metapac::timer::vals::Ocm::PWMMODE2, + OutputCompareMode::ForceInactive => stm32_metapac::timer::vals::Ocm::FORCE_INACTIVE, + OutputCompareMode::ForceActive => stm32_metapac::timer::vals::Ocm::FORCE_ACTIVE, + OutputCompareMode::PwmMode1 => stm32_metapac::timer::vals::Ocm::PWM_MODE1, + OutputCompareMode::PwmMode2 => stm32_metapac::timer::vals::Ocm::PWM_MODE2, } } } @@ -179,7 +181,7 @@ impl From for bool { /// Low-level timer driver. pub struct Timer<'d, T: CoreInstance> { - tim: PeripheralRef<'d, T>, + tim: Peri<'d, T>, } impl<'d, T: CoreInstance> Drop for Timer<'d, T> { @@ -190,14 +192,17 @@ impl<'d, T: CoreInstance> Drop for Timer<'d, T> { impl<'d, T: CoreInstance> Timer<'d, T> { /// Create a new timer driver. - pub fn new(tim: impl Peripheral

+ 'd) -> Self { - into_ref!(tim); - + pub fn new(tim: Peri<'d, T>) -> Self { rcc::enable_and_reset::(); Self { tim } } + pub(crate) unsafe fn clone_unchecked(&self) -> ManuallyDrop { + let tim = unsafe { self.tim.clone_unchecked() }; + ManuallyDrop::new(Self { tim }) + } + /// Get access to the virutal core 16bit timer registers. /// /// Note: This works even if the timer is more capable, because registers @@ -228,6 +233,11 @@ impl<'d, T: CoreInstance> Timer<'d, T> { self.regs_core().cnt().write(|r| r.set_cnt(0)); } + /// get the capability of the timer + pub fn bits(&self) -> TimerBits { + T::BITS + } + /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. /// /// This means that in the default edge-aligned mode, @@ -235,16 +245,28 @@ impl<'d, T: CoreInstance> Timer<'d, T> { /// In center-aligned mode (which not all timers support), the wrap-around frequency is effectively halved /// because it needs to count up and down. pub fn set_frequency(&self, frequency: Hertz) { + match T::BITS { + TimerBits::Bits16 => { + self.set_frequency_internal(frequency, 16); + } + #[cfg(not(stm32l0))] + TimerBits::Bits32 => { + self.set_frequency_internal(frequency, 32); + } + } + } + + pub(crate) fn set_frequency_internal(&self, frequency: Hertz, max_divide_by_bits: u8) { let f = frequency.0; assert!(f > 0); let timer_f = T::frequency().0; + let pclk_ticks_per_timer_period = (timer_f / f) as u64; + let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << max_divide_by_bits)).try_into()); + let divide_by = pclk_ticks_per_timer_period / (u64::from(psc) + 1); + match T::BITS { TimerBits::Bits16 => { - let pclk_ticks_per_timer_period = timer_f / f; - let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 16)).try_into()); - let divide_by = pclk_ticks_per_timer_period / (u32::from(psc) + 1); - // the timer counts `0..=arr`, we want it to count `0..divide_by` let arr = unwrap!(u16::try_from(divide_by - 1)); @@ -252,16 +274,12 @@ impl<'d, T: CoreInstance> Timer<'d, T> { regs.psc().write_value(psc); regs.arr().write(|r| r.set_arr(arr)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); regs.egr().write(|r| r.set_ug(true)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); } #[cfg(not(stm32l0))] TimerBits::Bits32 => { - 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 divide_by = pclk_ticks_per_timer_period / (u64::from(psc) + 1); - // the timer counts `0..=arr`, we want it to count `0..divide_by` let arr: u32 = unwrap!(u32::try_from(divide_by - 1)); @@ -269,9 +287,9 @@ impl<'d, T: CoreInstance> Timer<'d, T> { regs.psc().write_value(psc); regs.arr().write_value(arr); - regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); regs.egr().write(|r| r.set_ug(true)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); } } } @@ -405,6 +423,36 @@ impl<'d, T: GeneralInstance1Channel> Timer<'d, T> { TimerBits::Bits32 => self.regs_gp32_unchecked().arr().read(), } } + + /// Set the max compare value. + /// + /// An update event is generated to load the new value. The update event is + /// generated such that it will not cause an interrupt or DMA request. + pub fn set_max_compare_value(&self, ticks: u32) { + match T::BITS { + TimerBits::Bits16 => { + let arr = unwrap!(u16::try_from(ticks)); + + let regs = self.regs_1ch(); + regs.arr().write(|r| r.set_arr(arr)); + + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); + regs.egr().write(|r| r.set_ug(true)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); + } + #[cfg(not(stm32l0))] + TimerBits::Bits32 => { + let arr = ticks; + + let regs = self.regs_gp32_unchecked(); + regs.arr().write_value(arr); + + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); + regs.egr().write(|r| r.set_ug(true)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); + } + } + } } impl<'d, T: GeneralInstance2Channel> Timer<'d, T> { diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 25782ee13..b29382fc8 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -2,12 +2,14 @@ use core::marker::PhantomData; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(stm32l0))] pub mod complementary_pwm; pub mod input_capture; pub mod low_level; +pub mod one_pulse; pub mod pwm_input; pub mod qei; pub mod simple_pwm; @@ -40,6 +42,15 @@ impl Channel { } } +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} +/// Channel 3 marker type. +pub enum Ch3 {} +/// Channel 4 marker type. +pub enum Ch4 {} + /// Amount of bits of a timer. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -58,15 +69,14 @@ struct State { impl State { const fn new() -> Self { - const NEW_AW: AtomicWaker = AtomicWaker::new(); Self { - up_waker: NEW_AW, - cc_waker: [NEW_AW; 4], + up_waker: AtomicWaker::new(), + cc_waker: [const { AtomicWaker::new() }; 4], } } } -trait SealedInstance: RccPeripheral { +trait SealedInstance: RccPeripheral + PeripheralType { /// Async state for this timer fn state() -> &'static State; } diff --git a/embassy-stm32/src/timer/one_pulse.rs b/embassy-stm32/src/timer/one_pulse.rs new file mode 100644 index 000000000..933165ef9 --- /dev/null +++ b/embassy-stm32/src/timer/one_pulse.rs @@ -0,0 +1,383 @@ +//! One pulse mode driver. + +use core::future::Future; +use core::marker::PhantomData; +use core::mem::ManuallyDrop; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use super::low_level::{ + CountingMode, FilterValue, InputCaptureMode, InputTISelection, SlaveMode, Timer, TriggerSource, +}; +use super::{ + CaptureCompareInterruptHandler, Channel, Channel1Pin, Channel2Pin, ExternalTriggerPin, GeneralInstance4Channel, +}; +pub use super::{Ch1, Ch2}; +use crate::gpio::{AfType, AnyPin, Pull}; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::pac::timer::vals::Etp; +use crate::time::Hertz; +use crate::Peri; + +/// External input marker type. +pub enum Ext {} + +/// External trigger pin trigger polarity. +#[derive(Clone, Copy)] +pub enum ExternalTriggerPolarity { + /// Rising edge only. + Rising, + /// Falling edge only. + Falling, +} + +impl From for Etp { + fn from(mode: ExternalTriggerPolarity) -> Self { + match mode { + ExternalTriggerPolarity::Rising => 0.into(), + ExternalTriggerPolarity::Falling => 1.into(), + } + } +} + +/// Trigger pin wrapper. +/// +/// This wraps a pin to make it usable as a timer trigger. +pub struct TriggerPin<'d, T, C> { + _pin: Peri<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: GeneralInstance4Channel> TriggerPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " trigger pin instance.")] + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>, pull: Pull) -> Self { + pin.set_as_af(pin.af_num(), AfType::input(pull)); + TriggerPin { + _pin: pin.into(), + phantom: PhantomData, + } + } + } + }; +} + +channel_impl!(new_ch1, Ch1, Channel1Pin); +channel_impl!(new_ch2, Ch2, Channel2Pin); +channel_impl!(new_ext, Ext, ExternalTriggerPin); + +/// One pulse driver. +/// +/// Generates a pulse after a trigger and some configurable delay. +pub struct OnePulse<'d, T: GeneralInstance4Channel> { + inner: Timer<'d, T>, +} + +impl<'d, T: GeneralInstance4Channel> OnePulse<'d, T> { + /// Create a new one pulse driver. + /// + /// The pulse is triggered by a channel 1 input pin on both rising and + /// falling edges. Channel 1 will unusable as an output. + pub fn new_ch1_edge_detect( + tim: Peri<'d, T>, + _pin: TriggerPin<'d, T, Ch1>, + _irq: impl Binding> + 'd, + freq: Hertz, + pulse_end: u32, + counting_mode: CountingMode, + ) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.set_trigger_source(TriggerSource::TI1F_ED); + this.inner + .set_input_ti_selection(Channel::Ch1, InputTISelection::Normal); + this.inner + .set_input_capture_filter(Channel::Ch1, FilterValue::NO_FILTER); + this.new_inner(freq, pulse_end, counting_mode); + + this + } + + /// Create a new one pulse driver. + /// + /// The pulse is triggered by a channel 1 input pin. Channel 1 will unusable + /// as an output. + pub fn new_ch1( + tim: Peri<'d, T>, + _pin: TriggerPin<'d, T, Ch1>, + _irq: impl Binding> + 'd, + freq: Hertz, + pulse_end: u32, + counting_mode: CountingMode, + capture_mode: InputCaptureMode, + ) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.set_trigger_source(TriggerSource::TI1FP1); + this.inner + .set_input_ti_selection(Channel::Ch1, InputTISelection::Normal); + this.inner + .set_input_capture_filter(Channel::Ch1, FilterValue::NO_FILTER); + this.inner.set_input_capture_mode(Channel::Ch1, capture_mode); + this.new_inner(freq, pulse_end, counting_mode); + + this + } + + /// Create a new one pulse driver. + /// + /// The pulse is triggered by a channel 2 input pin. Channel 2 will unusable + /// as an output. + pub fn new_ch2( + tim: Peri<'d, T>, + _pin: TriggerPin<'d, T, Ch1>, + _irq: impl Binding> + 'd, + freq: Hertz, + pulse_end: u32, + counting_mode: CountingMode, + capture_mode: InputCaptureMode, + ) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.set_trigger_source(TriggerSource::TI2FP2); + this.inner + .set_input_ti_selection(Channel::Ch2, InputTISelection::Normal); + this.inner + .set_input_capture_filter(Channel::Ch2, FilterValue::NO_FILTER); + this.inner.set_input_capture_mode(Channel::Ch2, capture_mode); + this.new_inner(freq, pulse_end, counting_mode); + + this + } + + /// Create a new one pulse driver. + /// + /// The pulse is triggered by a external trigger input pin. + pub fn new_ext( + tim: Peri<'d, T>, + _pin: TriggerPin<'d, T, Ext>, + _irq: impl Binding> + 'd, + freq: Hertz, + pulse_end: u32, + counting_mode: CountingMode, + polarity: ExternalTriggerPolarity, + ) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.regs_gp16().smcr().modify(|r| { + r.set_etp(polarity.into()); + // No pre-scaling + r.set_etps(0.into()); + // No filtering + r.set_etf(FilterValue::NO_FILTER); + }); + this.inner.set_trigger_source(TriggerSource::ETRF); + this.new_inner(freq, pulse_end, counting_mode); + + this + } + + fn new_inner(&mut self, freq: Hertz, pulse_end: u32, counting_mode: CountingMode) { + self.inner.set_counting_mode(counting_mode); + self.inner.set_tick_freq(freq); + self.inner.set_max_compare_value(pulse_end); + self.inner.regs_core().cr1().modify(|r| r.set_opm(true)); + // Required for advanced timers, see GeneralInstance4Channel for details + self.inner.enable_outputs(); + self.inner.set_slave_mode(SlaveMode::TRIGGER_MODE); + + T::CaptureCompareInterrupt::unpend(); + unsafe { T::CaptureCompareInterrupt::enable() }; + } + + /// Get the end of the pulse in ticks from the trigger. + pub fn pulse_end(&self) -> u32 { + let max = self.inner.get_max_compare_value(); + assert!(max < u32::MAX); + max + 1 + } + + /// Set the end of the pulse in ticks from the trigger. + pub fn set_pulse_end(&mut self, ticks: u32) { + self.inner.set_max_compare_value(ticks) + } + + /// Reset the timer on each trigger + #[cfg(not(stm32l0))] + pub fn set_reset_on_trigger(&mut self, reset: bool) { + let slave_mode = if reset { + SlaveMode::COMBINED_RESET_TRIGGER + } else { + SlaveMode::TRIGGER_MODE + }; + self.inner.set_slave_mode(slave_mode); + } + + /// Get a single channel + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn channel(&mut self, channel: Channel) -> OnePulseChannel<'_, T> { + OnePulseChannel { + inner: unsafe { self.inner.clone_unchecked() }, + channel, + } + } + + /// Channel 1 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch1(&mut self) -> OnePulseChannel<'_, T> { + self.channel(Channel::Ch1) + } + + /// Channel 2 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch2(&mut self) -> OnePulseChannel<'_, T> { + self.channel(Channel::Ch2) + } + + /// Channel 3 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch3(&mut self) -> OnePulseChannel<'_, T> { + self.channel(Channel::Ch3) + } + + /// Channel 4 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch4(&mut self) -> OnePulseChannel<'_, T> { + self.channel(Channel::Ch4) + } + + /// Splits a [`OnePulse`] into four output channels. + // TODO: I hate the name "split" + pub fn split(self) -> OnePulseChannels<'static, T> + where + // must be static because the timer will never be dropped/disabled + 'd: 'static, + { + // without this, the timer would be disabled at the end of this function + let timer = ManuallyDrop::new(self.inner); + + let ch = |channel| OnePulseChannel { + inner: unsafe { timer.clone_unchecked() }, + channel, + }; + + OnePulseChannels { + ch1: ch(Channel::Ch1), + ch2: ch(Channel::Ch2), + ch3: ch(Channel::Ch3), + ch4: ch(Channel::Ch4), + } + } +} + +/// A group of four [`OnePulseChannel`]s, obtained from [`OnePulse::split`]. +pub struct OnePulseChannels<'d, T: GeneralInstance4Channel> { + /// Channel 1 + pub ch1: OnePulseChannel<'d, T>, + /// Channel 2 + pub ch2: OnePulseChannel<'d, T>, + /// Channel 3 + pub ch3: OnePulseChannel<'d, T>, + /// Channel 4 + pub ch4: OnePulseChannel<'d, T>, +} + +/// A single channel of a one pulse-configured timer, obtained from +/// [`OnePulse::split`],[`OnePulse::channel`], [`OnePulse::ch1`], etc. +/// +/// It is not possible to change the pulse end tick because the end tick +/// configuration is shared with all four channels. +pub struct OnePulseChannel<'d, T: GeneralInstance4Channel> { + inner: ManuallyDrop>, + channel: Channel, +} + +impl<'d, T: GeneralInstance4Channel> OnePulseChannel<'d, T> { + /// Get the end of the pulse in ticks from the trigger. + pub fn pulse_end(&self) -> u32 { + let max = self.inner.get_max_compare_value(); + assert!(max < u32::MAX); + max + 1 + } + + /// Get the width of the pulse in ticks. + pub fn pulse_width(&mut self) -> u32 { + self.pulse_end().saturating_sub(self.pulse_delay()) + } + + /// Get the start of the pulse in ticks from the trigger. + pub fn pulse_delay(&mut self) -> u32 { + self.inner.get_compare_value(self.channel) + } + + /// Set the start of the pulse in ticks from the trigger. + pub fn set_pulse_delay(&mut self, delay: u32) { + assert!(delay <= self.pulse_end()); + self.inner.set_compare_value(self.channel, delay); + } + + /// Set the pulse width in ticks. + pub fn set_pulse_width(&mut self, width: u32) { + assert!(width <= self.pulse_end()); + self.set_pulse_delay(self.pulse_end() - width); + } + + /// Waits until the trigger and following delay has passed. + pub async fn wait_for_pulse_start(&mut self) { + self.inner.enable_input_interrupt(self.channel, true); + + OnePulseFuture:: { + channel: self.channel, + phantom: PhantomData, + } + .await + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct OnePulseFuture { + channel: Channel, + phantom: PhantomData, +} + +impl<'d, T: GeneralInstance4Channel> Drop for OnePulseFuture { + fn drop(&mut self) { + critical_section::with(|_| { + let regs = unsafe { crate::pac::timer::TimGp16::from_ptr(T::regs()) }; + + // disable interrupt enable + regs.dier().modify(|w| w.set_ccie(self.channel.index(), false)); + }); + } +} + +impl<'d, T: GeneralInstance4Channel> Future for OnePulseFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + T::state().cc_waker[self.channel.index()].register(cx.waker()); + + let regs = unsafe { crate::pac::timer::TimGp16::from_ptr(T::regs()) }; + + let dier = regs.dier().read(); + if !dier.ccie(self.channel.index()) { + Poll::Ready(()) + } else { + Poll::Pending + } + } +} diff --git a/embassy-stm32/src/timer/pwm_input.rs b/embassy-stm32/src/timer/pwm_input.rs index e3eb6042a..98b798634 100644 --- a/embassy-stm32/src/timer/pwm_input.rs +++ b/embassy-stm32/src/timer/pwm_input.rs @@ -1,12 +1,10 @@ //! PWM Input driver. -use embassy_hal_internal::into_ref; - use super::low_level::{CountingMode, InputCaptureMode, InputTISelection, SlaveMode, Timer, TriggerSource}; use super::{Channel, Channel1Pin, Channel2Pin, GeneralInstance4Channel}; use crate::gpio::{AfType, Pull}; use crate::time::Hertz; -use crate::Peripheral; +use crate::Peri; /// PWM Input driver. pub struct PwmInput<'d, T: GeneralInstance4Channel> { @@ -16,34 +14,20 @@ pub struct PwmInput<'d, T: GeneralInstance4Channel> { impl<'d, T: GeneralInstance4Channel> PwmInput<'d, T> { /// Create a new PWM input driver. - pub fn new( - tim: impl Peripheral

+ 'd, - pin: impl Peripheral

> + 'd, - pull: Pull, - freq: Hertz, - ) -> Self { - into_ref!(pin); - + pub fn new(tim: Peri<'d, T>, pin: Peri<'d, impl Channel1Pin>, pull: Pull, freq: Hertz) -> Self { pin.set_as_af(pin.af_num(), AfType::input(pull)); Self::new_inner(tim, freq, Channel::Ch1, Channel::Ch2) } /// Create a new PWM input driver. - pub fn new_alt( - tim: impl Peripheral

+ 'd, - pin: impl Peripheral

> + 'd, - pull: Pull, - freq: Hertz, - ) -> Self { - into_ref!(pin); - + pub fn new_alt(tim: Peri<'d, T>, pin: Peri<'d, impl Channel2Pin>, pull: Pull, freq: Hertz) -> Self { pin.set_as_af(pin.af_num(), AfType::input(pull)); Self::new_inner(tim, freq, Channel::Ch2, Channel::Ch1) } - fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, ch1: Channel, ch2: Channel) -> Self { + fn new_inner(tim: Peri<'d, T>, freq: Hertz, ch1: Channel, ch2: Channel) -> Self { let mut inner = Timer::new(tim); inner.set_counting_mode(CountingMode::EdgeAlignedUp); diff --git a/embassy-stm32/src/timer/qei.rs b/embassy-stm32/src/timer/qei.rs index fc5835414..f3c81667c 100644 --- a/embassy-stm32/src/timer/qei.rs +++ b/embassy-stm32/src/timer/qei.rs @@ -2,13 +2,13 @@ use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, PeripheralRef}; use stm32_metapac::timer::vals; use super::low_level::Timer; +pub use super::{Ch1, Ch2}; use super::{Channel1Pin, Channel2Pin, GeneralInstance4Channel}; use crate::gpio::{AfType, AnyPin, Pull}; -use crate::Peripheral; +use crate::Peri; /// Counting direction pub enum Direction { @@ -18,14 +18,9 @@ pub enum Direction { Downcounting, } -/// Channel 1 marker type. -pub enum Ch1 {} -/// Channel 2 marker type. -pub enum Ch2 {} - /// Wrapper for using a pin with QEI. pub struct QeiPin<'d, T, Channel> { - _pin: PeripheralRef<'d, AnyPin>, + _pin: Peri<'d, AnyPin>, phantom: PhantomData<(T, Channel)>, } @@ -33,14 +28,13 @@ macro_rules! channel_impl { ($new_chx:ident, $channel:ident, $pin_trait:ident) => { impl<'d, T: GeneralInstance4Channel> QeiPin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " QEI pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { - into_ref!(pin); + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>) -> Self { critical_section::with(|_| { pin.set_low(); pin.set_as_af(pin.af_num(), AfType::input(Pull::None)); }); QeiPin { - _pin: pin.map_into(), + _pin: pin.into(), phantom: PhantomData, } } @@ -58,11 +52,11 @@ pub struct Qei<'d, T: GeneralInstance4Channel> { impl<'d, T: GeneralInstance4Channel> Qei<'d, T> { /// Create a new quadrature decoder driver. - pub fn new(tim: impl Peripheral

+ 'd, _ch1: QeiPin<'d, T, Ch1>, _ch2: QeiPin<'d, T, Ch2>) -> Self { + pub fn new(tim: Peri<'d, T>, _ch1: QeiPin<'d, T, Ch1>, _ch2: QeiPin<'d, T, Ch2>) -> Self { Self::new_inner(tim) } - fn new_inner(tim: impl Peripheral

+ 'd) -> Self { + fn new_inner(tim: Peri<'d, T>) -> Self { let inner = Timer::new(tim); let r = inner.regs_gp16(); diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index b7771bd64..f7f433154 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -1,14 +1,15 @@ //! Simple PWM driver. use core::marker::PhantomData; - -use embassy_hal_internal::{into_ref, PeripheralRef}; +use core::mem::ManuallyDrop; use super::low_level::{CountingMode, OutputCompareMode, OutputPolarity, Timer}; -use super::{Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, GeneralInstance4Channel}; +use super::{Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, GeneralInstance4Channel, TimerBits}; +#[cfg(gpio_v2)] +use crate::gpio::Pull; use crate::gpio::{AfType, AnyPin, OutputType, Speed}; use crate::time::Hertz; -use crate::Peripheral; +use crate::Peri; /// Channel 1 marker type. pub enum Ch1 {} @@ -23,22 +24,54 @@ pub enum Ch4 {} /// /// This wraps a pin to make it usable with PWM. pub struct PwmPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, + _pin: Peri<'d, AnyPin>, phantom: PhantomData<(T, C)>, } +/// PWM pin config +/// +/// This configures the pwm pin settings +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PwmPinConfig { + /// PWM Pin output type + pub output_type: OutputType, + /// PWM Pin speed + pub speed: Speed, + /// PWM Pin pull type + #[cfg(gpio_v2)] + pub pull: Pull, +} + macro_rules! channel_impl { - ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + ($new_chx:ident, $new_chx_with_config:ident, $channel:ident, $pin_trait:ident) => { impl<'d, T: GeneralInstance4Channel> PwmPin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] - pub fn $new_chx(pin: impl Peripheral

> + 'd, output_type: OutputType) -> Self { - into_ref!(pin); + pub fn $new_chx(pin: Peri<'d, impl $pin_trait>, output_type: OutputType) -> Self { critical_section::with(|_| { pin.set_low(); pin.set_as_af(pin.af_num(), AfType::output(output_type, Speed::VeryHigh)); }); PwmPin { - _pin: pin.map_into(), + _pin: pin.into(), + phantom: PhantomData, + } + } + + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance with config.")] + pub fn $new_chx_with_config(pin: Peri<'d, impl $pin_trait>, pin_config: PwmPinConfig) -> Self { + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + #[cfg(gpio_v1)] + AfType::output(pin_config.output_type, pin_config.speed), + #[cfg(gpio_v2)] + AfType::output_pull(pin_config.output_type, pin_config.speed, pin_config.pull), + ); + }); + PwmPin { + _pin: pin.into(), phantom: PhantomData, } } @@ -46,10 +79,115 @@ macro_rules! channel_impl { }; } -channel_impl!(new_ch1, Ch1, Channel1Pin); -channel_impl!(new_ch2, Ch2, Channel2Pin); -channel_impl!(new_ch3, Ch3, Channel3Pin); -channel_impl!(new_ch4, Ch4, Channel4Pin); +channel_impl!(new_ch1, new_ch1_with_config, Ch1, Channel1Pin); +channel_impl!(new_ch2, new_ch2_with_config, Ch2, Channel2Pin); +channel_impl!(new_ch3, new_ch3_with_config, Ch3, Channel3Pin); +channel_impl!(new_ch4, new_ch4_with_config, Ch4, Channel4Pin); + +/// A single channel of a pwm, obtained from [`SimplePwm::split`], +/// [`SimplePwm::channel`], [`SimplePwm::ch1`], etc. +/// +/// It is not possible to change the pwm frequency because +/// the frequency configuration is shared with all four channels. +pub struct SimplePwmChannel<'d, T: GeneralInstance4Channel> { + timer: ManuallyDrop>, + channel: Channel, +} + +// TODO: check for RMW races +impl<'d, T: GeneralInstance4Channel> SimplePwmChannel<'d, T> { + /// Enable the given channel. + pub fn enable(&mut self) { + self.timer.enable_channel(self.channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.timer.enable_channel(self.channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.timer.get_channel_enable_state(self.channel) + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn max_duty_cycle(&self) -> u16 { + let max = self.timer.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 + } + + /// Set the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included. + pub fn set_duty_cycle(&mut self, duty: u16) { + assert!(duty <= (*self).max_duty_cycle()); + self.timer.set_compare_value(self.channel, duty.into()) + } + + /// Set the duty cycle to 0%, or always inactive. + pub fn set_duty_cycle_fully_off(&mut self) { + self.set_duty_cycle(0); + } + + /// Set the duty cycle to 100%, or always active. + pub fn set_duty_cycle_fully_on(&mut self) { + self.set_duty_cycle((*self).max_duty_cycle()); + } + + /// Set the duty cycle to `num / denom`. + /// + /// The caller is responsible for ensuring that `num` is less than or equal to `denom`, + /// and that `denom` is not zero. + pub fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) { + assert!(denom != 0); + assert!(num <= denom); + let duty = u32::from(num) * u32::from(self.max_duty_cycle()) / u32::from(denom); + + // This is safe because we know that `num <= denom`, so `duty <= self.max_duty_cycle()` (u16) + #[allow(clippy::cast_possible_truncation)] + self.set_duty_cycle(duty as u16); + } + + /// Set the duty cycle to `percent / 100` + /// + /// The caller is responsible for ensuring that `percent` is less than or equal to 100. + pub fn set_duty_cycle_percent(&mut self, percent: u8) { + self.set_duty_cycle_fraction(u16::from(percent), 100) + } + + /// Get the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`max_duty_cycle`](Self::max_duty_cycle) for 100% duty, both included. + pub fn current_duty_cycle(&self) -> u16 { + unwrap!(self.timer.get_compare_value(self.channel).try_into()) + } + + /// Set the output polarity for a given channel. + pub fn set_polarity(&mut self, polarity: OutputPolarity) { + self.timer.set_output_polarity(self.channel, polarity); + } + + /// Set the output compare mode for a given channel. + pub fn set_output_compare_mode(&mut self, mode: OutputCompareMode) { + self.timer.set_output_compare_mode(self.channel, mode); + } +} + +/// A group of four [`SimplePwmChannel`]s, obtained from [`SimplePwm::split`]. +pub struct SimplePwmChannels<'d, T: GeneralInstance4Channel> { + /// Channel 1 + pub ch1: SimplePwmChannel<'d, T>, + /// Channel 2 + pub ch2: SimplePwmChannel<'d, T>, + /// Channel 3 + pub ch3: SimplePwmChannel<'d, T>, + /// Channel 4 + pub ch4: SimplePwmChannel<'d, T>, +} /// Simple PWM driver. pub struct SimplePwm<'d, T: GeneralInstance4Channel> { @@ -59,7 +197,7 @@ pub struct SimplePwm<'d, T: GeneralInstance4Channel> { impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Create a new simple PWM driver. pub fn new( - tim: impl Peripheral

+ 'd, + tim: Peri<'d, T>, _ch1: Option>, _ch2: Option>, _ch3: Option>, @@ -70,7 +208,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { Self::new_inner(tim, freq, counting_mode) } - fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { + fn new_inner(tim: Peri<'d, T>, freq: Hertz, counting_mode: CountingMode) -> Self { let mut this = Self { inner: Timer::new(tim) }; this.inner.set_counting_mode(counting_mode); @@ -89,19 +227,76 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { this } - /// Enable the given channel. - pub fn enable(&mut self, channel: Channel) { - self.inner.enable_channel(channel, true); + /// Get a single channel + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn channel(&mut self, channel: Channel) -> SimplePwmChannel<'_, T> { + SimplePwmChannel { + timer: unsafe { self.inner.clone_unchecked() }, + channel, + } } - /// Disable the given channel. - pub fn disable(&mut self, channel: Channel) { - self.inner.enable_channel(channel, false); + /// Channel 1 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch1(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch1) } - /// Check whether given channel is enabled - pub fn is_enabled(&self, channel: Channel) -> bool { - self.inner.get_channel_enable_state(channel) + /// Channel 2 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch2(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch2) + } + + /// Channel 3 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch3(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch3) + } + + /// Channel 4 + /// + /// This is just a convenience wrapper around [`Self::channel`]. + /// + /// If you need to use multiple channels, use [`Self::split`]. + pub fn ch4(&mut self) -> SimplePwmChannel<'_, T> { + self.channel(Channel::Ch4) + } + + /// Splits a [`SimplePwm`] into four pwm channels. + /// + /// This returns all four channels, including channels that + /// aren't configured with a [`PwmPin`]. + // TODO: I hate the name "split" + pub fn split(self) -> SimplePwmChannels<'static, T> + where + // must be static because the timer will never be dropped/disabled + 'd: 'static, + { + // without this, the timer would be disabled at the end of this function + let timer = ManuallyDrop::new(self.inner); + + let ch = |channel| SimplePwmChannel { + timer: unsafe { timer.clone_unchecked() }, + channel, + }; + + SimplePwmChannels { + ch1: ch(Channel::Ch1), + ch2: ch(Channel::Ch2), + ch3: ch(Channel::Ch3), + ch4: ch(Channel::Ch4), + } } /// Set PWM frequency. @@ -109,63 +304,34 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { /// Note: when you call this, the max duty value changes, so you will have to /// call `set_duty` on all channels with the duty calculated based on the new max duty. pub fn set_frequency(&mut self, freq: Hertz) { + // TODO: prevent ARR = u16::MAX? let multiplier = if self.inner.get_counting_mode().is_center_aligned() { 2u8 } else { 1u8 }; - self.inner.set_frequency(freq * multiplier); + self.inner.set_frequency_internal(freq * multiplier, 16); } /// Get max duty value. /// /// This value depends on the configured frequency and the timer's clock rate from RCC. - pub fn get_max_duty(&self) -> u32 { - self.inner.get_max_compare_value() + 1 - } - - /// Set the duty for a given channel. - /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn set_duty(&mut self, channel: Channel, duty: u32) { - assert!(duty <= self.get_max_duty()); - self.inner.set_compare_value(channel, duty) - } - - /// Get the duty for a given channel. - /// - /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. - pub fn get_duty(&self, channel: Channel) -> u32 { - self.inner.get_compare_value(channel) - } - - /// Set the output polarity for a given channel. - pub fn set_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { - 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); + pub fn max_duty_cycle(&self) -> u16 { + let max = self.inner.get_max_compare_value(); + assert!(max < u16::MAX as u32); + max as u16 + 1 } /// Generate a sequence of PWM waveform /// - /// Note: + /// Note: /// you will need to provide corresponding TIMx_UP DMA channel to use this method. - pub async fn waveform_up( - &mut self, - dma: impl Peripheral

>, - channel: Channel, - duty: &[u16], - ) { - into_ref!(dma); - + pub async fn waveform_up(&mut self, dma: Peri<'_, impl super::UpDma>, channel: Channel, duty: &[u16]) { #[allow(clippy::let_unit_value)] // eg. stm32f334 let req = dma.request(); - let original_duty_state = self.get_duty(channel); - let original_enable_state = self.is_enabled(channel); + let original_duty_state = self.channel(channel).current_duty_cycle(); + let original_enable_state = self.channel(channel).is_enabled(); let original_update_dma_state = self.inner.get_update_dma_state(); if !original_update_dma_state { @@ -173,7 +339,7 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { } if !original_enable_state { - self.enable(channel); + self.channel(channel).enable(); } unsafe { @@ -190,10 +356,10 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { }; Transfer::new_write( - &mut dma, + dma, req, duty, - self.inner.regs_1ch().ccr(channel.index()).as_ptr() as *mut _, + self.inner.regs_1ch().ccr(channel.index()).as_ptr() as *mut u16, dma_transfer_option, ) .await @@ -201,10 +367,10 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { // restore output compare state if !original_enable_state { - self.disable(channel); + self.channel(channel).disable(); } - self.set_duty(channel, original_duty_state); + self.channel(channel).set_duty_cycle(original_duty_state); // Since DMA is closed before timer update event trigger DMA is turn off, // this can almost always trigger a DMA FIFO error. @@ -215,33 +381,111 @@ impl<'d, T: GeneralInstance4Channel> SimplePwm<'d, T> { self.inner.enable_update_dma(false); } } + + /// Generate a multichannel sequence of PWM waveforms using DMA triggered by timer update events. + /// + /// This method utilizes the timer's DMA burst transfer capability to update multiple CCRx registers + /// in sequence on each update event (UEV). The data is written via the DMAR register using the + /// DMA base address (DBA) and burst length (DBL) configured in the DCR register. + /// + /// The `duty` buffer must be structured as a flattened 2D array in row-major order, where each row + /// represents a single update event and each column corresponds to a specific timer channel (starting + /// from `starting_channel` up to and including `ending_channel`). + /// + /// For example, if using channels 1 through 4, a buffer of 4 update steps might look like: + /// + /// let dma_buf: [u16; 16] = [ + /// ch1_duty_1, ch2_duty_1, ch3_duty_1, ch4_duty_1, // update 1 + /// ch1_duty_2, ch2_duty_2, ch3_duty_2, ch4_duty_2, // update 2 + /// ch1_duty_3, ch2_duty_3, ch3_duty_3, ch4_duty_3, // update 3 + /// ch1_duty_4, ch2_duty_4, ch3_duty_4, ch4_duty_4, // update 4 + /// ]; + /// + /// Each group of N values (where N = number of channels) is transferred on one update event, + /// updating the duty cycles of all selected channels simultaneously. + /// + /// Note: + /// you will need to provide corresponding TIMx_UP DMA channel to use this method. + pub async fn waveform_up_multi_channel( + &mut self, + dma: Peri<'_, impl super::UpDma>, + starting_channel: Channel, + ending_channel: Channel, + duty: &[u16], + ) { + let cr1_addr = self.inner.regs_gp16().cr1().as_ptr() as u32; + let start_ch_index = starting_channel.index(); + let end_ch_index = ending_channel.index(); + + assert!(start_ch_index <= end_ch_index); + + let ccrx_addr = self.inner.regs_gp16().ccr(start_ch_index).as_ptr() as u32; + self.inner + .regs_gp16() + .dcr() + .modify(|w| w.set_dba(((ccrx_addr - cr1_addr) / 4) as u8)); + self.inner + .regs_gp16() + .dcr() + .modify(|w| w.set_dbl((end_ch_index - start_ch_index) as u8)); + + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + let original_update_dma_state = self.inner.get_update_dma_state(); + if !original_update_dma_state { + self.inner.enable_update_dma(true); + } + + 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::Incr4, + ..Default::default() + }; + + Transfer::new_write( + dma, + req, + duty, + self.inner.regs_gp16().dmar().as_ptr() as *mut u16, + dma_transfer_option, + ) + .await + }; + + if !original_update_dma_state { + self.inner.enable_update_dma(false); + } + } } macro_rules! impl_waveform_chx { ($fn_name:ident, $dma_ch:ident, $cc_ch:ident) => { impl<'d, T: GeneralInstance4Channel> 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

>, duty: &[u16]) { + pub async fn $fn_name(&mut self, dma: Peri<'_, impl super::$dma_ch>, duty: &[u16]) { use crate::pac::timer::vals::Ccds; - into_ref!(dma); - #[allow(clippy::let_unit_value)] // eg. stm32f334 let req = dma.request(); let cc_channel = 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_duty_state = self.channel(cc_channel).current_duty_cycle(); + let original_enable_state = self.channel(cc_channel).is_enabled(); + let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ON_UPDATE; 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) + self.inner.set_cc_dma_selection(Ccds::ON_UPDATE) } if !original_cc_dma_enabled { @@ -249,7 +493,7 @@ macro_rules! impl_waveform_chx { } if !original_enable_state { - self.enable(cc_channel); + self.channel(cc_channel).enable(); } unsafe { @@ -265,22 +509,41 @@ macro_rules! impl_waveform_chx { ..Default::default() }; - Transfer::new_write( - &mut dma, - req, - duty, - self.inner.regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut _, - dma_transfer_option, - ) - .await + match self.inner.bits() { + TimerBits::Bits16 => { + Transfer::new_write( + dma, + req, + duty, + self.inner.regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut u16, + dma_transfer_option, + ) + .await + } + #[cfg(not(any(stm32l0)))] + TimerBits::Bits32 => { + #[cfg(not(any(bdma, gpdma)))] + panic!("unsupported timer bits"); + + #[cfg(any(bdma, gpdma))] + Transfer::new_write( + dma, + req, + duty, + self.inner.regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut u32, + dma_transfer_option, + ) + .await + } + }; }; // restore output compare state if !original_enable_state { - self.disable(cc_channel); + self.channel(cc_channel).disable(); } - self.set_duty(cc_channel, original_duty_state); + self.channel(cc_channel).set_duty_cycle(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. @@ -292,7 +555,7 @@ macro_rules! impl_waveform_chx { } if !original_cc_dma_on_update { - self.inner.set_cc_dma_selection(Ccds::ONCOMPARE) + self.inner.set_cc_dma_selection(Ccds::ON_COMPARE) } } } @@ -304,6 +567,41 @@ impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2); impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3); impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4); +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::ErrorType for SimplePwmChannel<'d, T> { + type Error = core::convert::Infallible; +} + +impl<'d, T: GeneralInstance4Channel> embedded_hal_1::pwm::SetDutyCycle for SimplePwmChannel<'d, T> { + fn max_duty_cycle(&self) -> u16 { + self.max_duty_cycle() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + self.set_duty_cycle(duty); + Ok(()) + } + + fn set_duty_cycle_fully_off(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_off(); + Ok(()) + } + + fn set_duty_cycle_fully_on(&mut self) -> Result<(), Self::Error> { + self.set_duty_cycle_fully_on(); + Ok(()) + } + + fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { + self.set_duty_cycle_fraction(num, denom); + Ok(()) + } + + fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> { + self.set_duty_cycle_percent(percent); + Ok(()) + } +} + impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> { type Channel = Channel; type Time = Hertz; @@ -330,7 +628,7 @@ impl<'d, T: GeneralInstance4Channel> embedded_hal_02::Pwm for SimplePwm<'d, T> { } fn set_duty(&mut self, channel: Self::Channel, duty: Self::Duty) { - assert!(duty <= self.get_max_duty()); + assert!(duty <= self.max_duty_cycle() as u32); self.inner.set_compare_value(channel, duty) } diff --git a/embassy-stm32/src/tsc/acquisition_banks.rs b/embassy-stm32/src/tsc/acquisition_banks.rs new file mode 100644 index 000000000..6791ef6c1 --- /dev/null +++ b/embassy-stm32/src/tsc/acquisition_banks.rs @@ -0,0 +1,209 @@ +use super::io_pin::*; +#[cfg(any(tsc_v2, tsc_v3))] +use super::pin_groups::G7; +#[cfg(tsc_v3)] +use super::pin_groups::G8; +use super::pin_groups::{pin_roles, G1, G2, G3, G4, G5, G6}; +use super::types::{Group, GroupStatus}; +use super::TSC_NUM_GROUPS; + +/// Represents a collection of TSC (Touch Sensing Controller) pins for an acquisition bank. +/// +/// This struct holds optional `tsc::IOPin` values for each TSC group, allowing for flexible +/// configuration of TSC acquisition banks. Each field corresponds to a specific TSC group +/// and can be set to `Some(tsc::IOPin)` if that group is to be included in the acquisition, +/// or `None` if it should be excluded. +#[allow(missing_docs)] +#[derive(Default)] +pub struct AcquisitionBankPins { + pub g1_pin: Option>, + pub g2_pin: Option>, + pub g3_pin: Option>, + pub g4_pin: Option>, + pub g5_pin: Option>, + pub g6_pin: Option>, + #[cfg(any(tsc_v2, tsc_v3))] + pub g7_pin: Option>, + #[cfg(tsc_v3)] + pub g8_pin: Option>, +} + +impl AcquisitionBankPins { + /// Returns an iterator over the pins in this acquisition bank. + /// + /// This method allows for easy traversal of all configured pins in the bank. + pub fn iter(&self) -> AcquisitionBankPinsIterator { + AcquisitionBankPinsIterator(AcquisitionBankIterator::new(self)) + } +} + +/// Iterator for TSC acquisition banks. +/// +/// This iterator allows traversing through the pins of a `AcquisitionBankPins` struct, +/// yielding each configured pin in order of the TSC groups. +pub struct AcquisitionBankIterator<'a> { + pins: &'a AcquisitionBankPins, + current_group: u8, +} + +impl<'a> AcquisitionBankIterator<'a> { + fn new(pins: &'a AcquisitionBankPins) -> Self { + Self { pins, current_group: 0 } + } + + fn next_pin(&mut self) -> Option { + while self.current_group < TSC_NUM_GROUPS as u8 { + let pin = match self.current_group { + 0 => self.pins.g1_pin.map(IOPinWithRole::get_pin), + 1 => self.pins.g2_pin.map(IOPinWithRole::get_pin), + 2 => self.pins.g3_pin.map(IOPinWithRole::get_pin), + 3 => self.pins.g4_pin.map(IOPinWithRole::get_pin), + 4 => self.pins.g5_pin.map(IOPinWithRole::get_pin), + 5 => self.pins.g6_pin.map(IOPinWithRole::get_pin), + #[cfg(any(tsc_v2, tsc_v3))] + 6 => self.pins.g7_pin.map(IOPinWithRole::get_pin), + #[cfg(tsc_v3)] + 7 => self.pins.g8_pin.map(IOPinWithRole::get_pin), + _ => None, + }; + self.current_group += 1; + if pin.is_some() { + return pin; + } + } + None + } +} + +/// Iterator for TSC acquisition bank pins. +/// +/// This iterator yields `tsc::IOPin` values for each configured pin in the acquisition bank. +pub struct AcquisitionBankPinsIterator<'a>(AcquisitionBankIterator<'a>); + +impl<'a> Iterator for AcquisitionBankPinsIterator<'a> { + type Item = IOPin; + + fn next(&mut self) -> Option { + self.0.next_pin() + } +} + +impl AcquisitionBankPins { + /// Returns an iterator over the available pins in the bank + pub fn pins_iterator(&self) -> AcquisitionBankPinsIterator { + AcquisitionBankPinsIterator(AcquisitionBankIterator::new(self)) + } +} + +/// Represents a collection of TSC pins to be acquired simultaneously. +/// +/// This struct contains a set of pins to be used in a TSC acquisition with a pre-computed and +/// verified mask for efficiently setting up the TSC peripheral before performing an acquisition. +/// It ensures that only one channel pin per TSC group is included, adhering to hardware limitations. +pub struct AcquisitionBank { + pub(super) pins: AcquisitionBankPins, + pub(super) mask: u32, +} + +impl AcquisitionBank { + /// Returns an iterator over the available pins in the bank. + pub fn pins_iterator(&self) -> AcquisitionBankPinsIterator { + self.pins.pins_iterator() + } + + /// Returns the mask for this bank. + pub fn mask(&self) -> u32 { + self.mask + } + + /// Retrieves the TSC I/O pin for a given group in this acquisition bank. + /// + /// # Arguments + /// * `group` - The TSC group to retrieve the pin for. + /// + /// # Returns + /// An `Option` containing the pin if it exists for the given group, or `None` if not. + pub fn get_pin(&self, group: Group) -> Option { + match group { + Group::One => self.pins.g1_pin.map(|p| p.pin), + Group::Two => self.pins.g2_pin.map(|p| p.pin), + Group::Three => self.pins.g3_pin.map(|p| p.pin), + Group::Four => self.pins.g4_pin.map(|p| p.pin), + Group::Five => self.pins.g5_pin.map(|p| p.pin), + Group::Six => self.pins.g6_pin.map(|p| p.pin), + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => self.pins.g7_pin.map(|p| p.pin), + #[cfg(tsc_v3)] + Group::Eight => self.pins.g8_pin.map(|p| p.pin), + } + } +} + +/// Represents the status of all TSC groups in an acquisition bank +#[derive(Default)] +pub struct AcquisitionBankStatus { + pub(super) groups: [Option; TSC_NUM_GROUPS], +} + +impl AcquisitionBankStatus { + /// Check if all groups in the bank are complete + pub fn all_complete(&self) -> bool { + self.groups + .iter() + .all(|&status| status.map_or(true, |s| s == GroupStatus::Complete)) + } + + /// Check if any group in the bank is ongoing + pub fn any_ongoing(&self) -> bool { + self.groups.iter().any(|&status| status == Some(GroupStatus::Ongoing)) + } + + /// Get the status of a specific group, if the group is present in the bank + pub fn get_group_status(&self, group: Group) -> Option { + let index: usize = group.into(); + self.groups[index] + } + + /// Iterator for groups present in the bank + pub fn iter(&self) -> impl Iterator + '_ { + self.groups.iter().enumerate().filter_map(|(group_num, status)| { + status.and_then(|s| Group::try_from(group_num).ok().map(|group| (group, s))) + }) + } +} + +/// Represents the result of a Touch Sensing Controller (TSC) acquisition for a specific pin. +/// +/// This struct contains a reference to the `tsc::IOPin` from which a value was read, +/// along with the actual sensor reading for that pin. It provides a convenient way +/// to associate TSC readings with their corresponding pins after an acquisition. +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug)] +pub struct ChannelReading { + /// The sensor reading value obtained from the TSC acquisition. + /// Lower values typically indicate a detected touch, while higher values indicate no touch. + pub sensor_value: u16, + + /// The `tsc::IOPin` associated with this reading. + /// This allows for easy identification of which pin the reading corresponds to. + pub tsc_pin: IOPin, +} + +/// Represents the readings from all TSC groups +#[derive(Default)] +pub struct AcquisitionBankReadings { + pub(super) groups: [Option; TSC_NUM_GROUPS], +} + +impl AcquisitionBankReadings { + /// Get the reading for a specific group, if the group is present in the bank + pub fn get_group_reading(&self, group: Group) -> Option { + let index: usize = group.into(); + self.groups[index] + } + + /// Iterator for readings for groups present in the bank + pub fn iter(&self) -> impl Iterator + '_ { + self.groups.iter().filter_map(|&x| x) + } +} diff --git a/embassy-stm32/src/tsc/config.rs b/embassy-stm32/src/tsc/config.rs new file mode 100644 index 000000000..efa1f9a0d --- /dev/null +++ b/embassy-stm32/src/tsc/config.rs @@ -0,0 +1,175 @@ +/// Charge transfer pulse cycles +#[allow(missing_docs)] +#[derive(Copy, Clone, PartialEq)] +pub enum ChargeTransferPulseCycle { + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, +} + +impl Into for ChargeTransferPulseCycle { + fn into(self) -> u8 { + match self { + ChargeTransferPulseCycle::_1 => 0, + ChargeTransferPulseCycle::_2 => 1, + ChargeTransferPulseCycle::_3 => 2, + ChargeTransferPulseCycle::_4 => 3, + ChargeTransferPulseCycle::_5 => 4, + ChargeTransferPulseCycle::_6 => 5, + ChargeTransferPulseCycle::_7 => 6, + ChargeTransferPulseCycle::_8 => 7, + ChargeTransferPulseCycle::_9 => 8, + ChargeTransferPulseCycle::_10 => 9, + ChargeTransferPulseCycle::_11 => 10, + ChargeTransferPulseCycle::_12 => 11, + ChargeTransferPulseCycle::_13 => 12, + ChargeTransferPulseCycle::_14 => 13, + ChargeTransferPulseCycle::_15 => 14, + ChargeTransferPulseCycle::_16 => 15, + } + } +} + +/// Max count +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum MaxCount { + _255, + _511, + _1023, + _2047, + _4095, + _8191, + _16383, +} + +impl Into for MaxCount { + fn into(self) -> u8 { + match self { + MaxCount::_255 => 0, + MaxCount::_511 => 1, + MaxCount::_1023 => 2, + MaxCount::_2047 => 3, + MaxCount::_4095 => 4, + MaxCount::_8191 => 5, + MaxCount::_16383 => 6, + } + } +} + +/// Prescaler divider +#[allow(missing_docs)] +#[derive(Copy, Clone, PartialEq)] +pub enum PGPrescalerDivider { + _1, + _2, + _4, + _8, + _16, + _32, + _64, + _128, +} + +impl Into for PGPrescalerDivider { + fn into(self) -> u8 { + match self { + PGPrescalerDivider::_1 => 0, + PGPrescalerDivider::_2 => 1, + PGPrescalerDivider::_4 => 2, + PGPrescalerDivider::_8 => 3, + PGPrescalerDivider::_16 => 4, + PGPrescalerDivider::_32 => 5, + PGPrescalerDivider::_64 => 6, + PGPrescalerDivider::_128 => 7, + } + } +} + +/// Error type for SSDeviation +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SSDeviationError { + /// The provided value is too low (0) + ValueTooLow, + /// The provided value is too high (greater than 128) + ValueTooHigh, +} + +/// Spread Spectrum Deviation +#[derive(Copy, Clone)] +pub struct SSDeviation(u8); +impl SSDeviation { + /// Create new deviation value, acceptable inputs are 1-128 + pub fn new(val: u8) -> Result { + if val == 0 { + return Err(SSDeviationError::ValueTooLow); + } else if val > 128 { + return Err(SSDeviationError::ValueTooHigh); + } + Ok(Self(val - 1)) + } +} + +impl Into for SSDeviation { + fn into(self) -> u8 { + self.0 + } +} + +/// Peripheral configuration +#[derive(Clone, Copy)] +pub struct Config { + /// Duration of high state of the charge transfer pulse + pub ct_pulse_high_length: ChargeTransferPulseCycle, + /// Duration of the low state of the charge transfer pulse + pub ct_pulse_low_length: ChargeTransferPulseCycle, + /// Enable/disable of spread spectrum feature + pub spread_spectrum: bool, + /// Adds variable number of periods of the SS clk to pulse high state + pub spread_spectrum_deviation: SSDeviation, + /// Selects AHB clock divider used to generate SS clk + pub spread_spectrum_prescaler: bool, + /// Selects AHB clock divider used to generate pulse generator clk + pub pulse_generator_prescaler: PGPrescalerDivider, + /// Maximum number of charge transfer pulses that can be generated before error + pub max_count_value: MaxCount, + /// Defines config of all IOs when no ongoing acquisition + pub io_default_mode: bool, + /// Polarity of sync input pin + pub synchro_pin_polarity: bool, + /// Acquisition starts when start bit is set or with sync pin input + pub acquisition_mode: bool, + /// Enable max count interrupt + pub max_count_interrupt: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + ct_pulse_high_length: ChargeTransferPulseCycle::_1, + ct_pulse_low_length: ChargeTransferPulseCycle::_1, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(1).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_1, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + } + } +} diff --git a/embassy-stm32/src/tsc/enums.rs b/embassy-stm32/src/tsc/enums.rs deleted file mode 100644 index 0d34a43ec..000000000 --- a/embassy-stm32/src/tsc/enums.rs +++ /dev/null @@ -1,238 +0,0 @@ -use core::ops::BitOr; - -/// Pin defines -#[allow(missing_docs)] -pub enum TscIOPin { - Group1Io1, - Group1Io2, - Group1Io3, - Group1Io4, - Group2Io1, - Group2Io2, - Group2Io3, - Group2Io4, - Group3Io1, - Group3Io2, - Group3Io3, - Group3Io4, - Group4Io1, - Group4Io2, - Group4Io3, - Group4Io4, - Group5Io1, - Group5Io2, - Group5Io3, - Group5Io4, - Group6Io1, - Group6Io2, - Group6Io3, - Group6Io4, - #[cfg(any(tsc_v2, tsc_v3))] - Group7Io1, - #[cfg(any(tsc_v2, tsc_v3))] - Group7Io2, - #[cfg(any(tsc_v2, tsc_v3))] - Group7Io3, - #[cfg(any(tsc_v2, tsc_v3))] - Group7Io4, - #[cfg(tsc_v3)] - Group8Io1, - #[cfg(tsc_v3)] - Group8Io2, - #[cfg(tsc_v3)] - Group8Io3, - #[cfg(tsc_v3)] - Group8Io4, -} - -impl BitOr for u32 { - type Output = u32; - fn bitor(self, rhs: TscIOPin) -> Self::Output { - let rhs: u32 = rhs.into(); - self | rhs - } -} - -impl BitOr for TscIOPin { - type Output = u32; - fn bitor(self, rhs: u32) -> Self::Output { - let val: u32 = self.into(); - val | rhs - } -} - -impl BitOr for TscIOPin { - type Output = u32; - fn bitor(self, rhs: Self) -> Self::Output { - let val: u32 = self.into(); - let rhs: u32 = rhs.into(); - val | rhs - } -} - -impl Into for TscIOPin { - fn into(self) -> u32 { - match self { - TscIOPin::Group1Io1 => 0x00000001, - TscIOPin::Group1Io2 => 0x00000002, - TscIOPin::Group1Io3 => 0x00000004, - TscIOPin::Group1Io4 => 0x00000008, - TscIOPin::Group2Io1 => 0x00000010, - TscIOPin::Group2Io2 => 0x00000020, - TscIOPin::Group2Io3 => 0x00000040, - TscIOPin::Group2Io4 => 0x00000080, - TscIOPin::Group3Io1 => 0x00000100, - TscIOPin::Group3Io2 => 0x00000200, - TscIOPin::Group3Io3 => 0x00000400, - TscIOPin::Group3Io4 => 0x00000800, - TscIOPin::Group4Io1 => 0x00001000, - TscIOPin::Group4Io2 => 0x00002000, - TscIOPin::Group4Io3 => 0x00004000, - TscIOPin::Group4Io4 => 0x00008000, - TscIOPin::Group5Io1 => 0x00010000, - TscIOPin::Group5Io2 => 0x00020000, - TscIOPin::Group5Io3 => 0x00040000, - TscIOPin::Group5Io4 => 0x00080000, - TscIOPin::Group6Io1 => 0x00100000, - TscIOPin::Group6Io2 => 0x00200000, - TscIOPin::Group6Io3 => 0x00400000, - TscIOPin::Group6Io4 => 0x00800000, - #[cfg(any(tsc_v2, tsc_v3))] - TscIOPin::Group7Io1 => 0x01000000, - #[cfg(any(tsc_v2, tsc_v3))] - TscIOPin::Group7Io2 => 0x02000000, - #[cfg(any(tsc_v2, tsc_v3))] - TscIOPin::Group7Io3 => 0x04000000, - #[cfg(any(tsc_v2, tsc_v3))] - TscIOPin::Group7Io4 => 0x08000000, - #[cfg(tsc_v3)] - TscIOPin::Group8Io1 => 0x10000000, - #[cfg(tsc_v3)] - TscIOPin::Group8Io2 => 0x20000000, - #[cfg(tsc_v3)] - TscIOPin::Group8Io3 => 0x40000000, - #[cfg(tsc_v3)] - TscIOPin::Group8Io4 => 0x80000000, - } - } -} - -/// Spread Spectrum Deviation -#[derive(Copy, Clone)] -pub struct SSDeviation(u8); -impl SSDeviation { - /// Create new deviation value, acceptable inputs are 1-128 - pub fn new(val: u8) -> Result { - if val == 0 || val > 128 { - return Err(()); - } - Ok(Self(val - 1)) - } -} - -impl Into for SSDeviation { - fn into(self) -> u8 { - self.0 - } -} - -/// Charge transfer pulse cycles -#[allow(missing_docs)] -#[derive(Copy, Clone, PartialEq)] -pub enum ChargeTransferPulseCycle { - _1, - _2, - _3, - _4, - _5, - _6, - _7, - _8, - _9, - _10, - _11, - _12, - _13, - _14, - _15, - _16, -} - -impl Into for ChargeTransferPulseCycle { - fn into(self) -> u8 { - match self { - ChargeTransferPulseCycle::_1 => 0, - ChargeTransferPulseCycle::_2 => 1, - ChargeTransferPulseCycle::_3 => 2, - ChargeTransferPulseCycle::_4 => 3, - ChargeTransferPulseCycle::_5 => 4, - ChargeTransferPulseCycle::_6 => 5, - ChargeTransferPulseCycle::_7 => 6, - ChargeTransferPulseCycle::_8 => 7, - ChargeTransferPulseCycle::_9 => 8, - ChargeTransferPulseCycle::_10 => 9, - ChargeTransferPulseCycle::_11 => 10, - ChargeTransferPulseCycle::_12 => 11, - ChargeTransferPulseCycle::_13 => 12, - ChargeTransferPulseCycle::_14 => 13, - ChargeTransferPulseCycle::_15 => 14, - ChargeTransferPulseCycle::_16 => 15, - } - } -} - -/// Prescaler divider -#[allow(missing_docs)] -#[derive(Copy, Clone, PartialEq)] -pub enum PGPrescalerDivider { - _1, - _2, - _4, - _8, - _16, - _32, - _64, - _128, -} - -impl Into for PGPrescalerDivider { - fn into(self) -> u8 { - match self { - PGPrescalerDivider::_1 => 0, - PGPrescalerDivider::_2 => 1, - PGPrescalerDivider::_4 => 2, - PGPrescalerDivider::_8 => 3, - PGPrescalerDivider::_16 => 4, - PGPrescalerDivider::_32 => 5, - PGPrescalerDivider::_64 => 6, - PGPrescalerDivider::_128 => 7, - } - } -} - -/// Max count -#[allow(missing_docs)] -#[derive(Copy, Clone)] -pub enum MaxCount { - _255, - _511, - _1023, - _2047, - _4095, - _8191, - _16383, -} - -impl Into for MaxCount { - fn into(self) -> u8 { - match self { - MaxCount::_255 => 0, - MaxCount::_511 => 1, - MaxCount::_1023 => 2, - MaxCount::_2047 => 3, - MaxCount::_4095 => 4, - MaxCount::_8191 => 5, - MaxCount::_16383 => 6, - } - } -} diff --git a/embassy-stm32/src/tsc/errors.rs b/embassy-stm32/src/tsc/errors.rs new file mode 100644 index 000000000..21f6441ba --- /dev/null +++ b/embassy-stm32/src/tsc/errors.rs @@ -0,0 +1,21 @@ +/// Represents errors that can occur when configuring or validating TSC pin groups. +#[derive(Debug)] +pub enum GroupError { + /// Error when a group has no sampling capacitor + NoSamplingCapacitor, + /// Error when a group has neither channel IOs nor a shield IO + NoChannelOrShield, + /// Error when a group has both channel IOs and a shield IO + MixedChannelAndShield, + /// Error when there is more than one shield IO across all groups + MultipleShields, +} + +/// Error returned when attempting to set an invalid channel pin as active in the TSC. +#[derive(Debug)] +pub enum AcquisitionBankError { + /// Indicates that one or more of the provided pins is not a valid channel pin. + InvalidChannelPin, + /// Indicates that multiple channels from the same group were provided. + MultipleChannelsPerGroup, +} diff --git a/embassy-stm32/src/tsc/io_pin.rs b/embassy-stm32/src/tsc/io_pin.rs new file mode 100644 index 000000000..ad2010ed7 --- /dev/null +++ b/embassy-stm32/src/tsc/io_pin.rs @@ -0,0 +1,200 @@ +use core::marker::PhantomData; +use core::ops::{BitAnd, BitOr, BitOrAssign}; + +use super::pin_roles; +use super::types::Group; + +/// Pin defines +#[allow(missing_docs)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum IOPin { + Group1Io1, + Group1Io2, + Group1Io3, + Group1Io4, + Group2Io1, + Group2Io2, + Group2Io3, + Group2Io4, + Group3Io1, + Group3Io2, + Group3Io3, + Group3Io4, + Group4Io1, + Group4Io2, + Group4Io3, + Group4Io4, + Group5Io1, + Group5Io2, + Group5Io3, + Group5Io4, + Group6Io1, + Group6Io2, + Group6Io3, + Group6Io4, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io1, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io2, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io3, + #[cfg(any(tsc_v2, tsc_v3))] + Group7Io4, + #[cfg(tsc_v3)] + Group8Io1, + #[cfg(tsc_v3)] + Group8Io2, + #[cfg(tsc_v3)] + Group8Io3, + #[cfg(tsc_v3)] + Group8Io4, +} + +/// Represents a TSC I/O pin with associated group and role information. +/// +/// This type combines an `tsc::IOPin` with phantom type parameters to statically +/// encode the pin's group and role. This allows for type-safe operations +/// on TSC pins within their specific contexts. +/// +/// - `Group`: A type parameter representing the TSC group (e.g., `G1`, `G2`). +/// - `Role`: A type parameter representing the pin's role (e.g., `Channel`, `Sample`). +#[derive(Clone, Copy, Debug)] +pub struct IOPinWithRole { + /// The underlying TSC I/O pin. + pub pin: IOPin, + pub(super) phantom: PhantomData<(Group, Role)>, +} + +impl IOPinWithRole { + pub(super) fn get_pin(wrapped_pin: IOPinWithRole) -> IOPin { + wrapped_pin.pin + } +} + +impl IOPin { + /// Maps this IOPin to the Group it belongs to. + /// + /// This method provides a convenient way to determine which Group + /// a specific TSC I/O pin is associated with. + pub const fn group(&self) -> Group { + match self { + IOPin::Group1Io1 | IOPin::Group1Io2 | IOPin::Group1Io3 | IOPin::Group1Io4 => Group::One, + IOPin::Group2Io1 | IOPin::Group2Io2 | IOPin::Group2Io3 | IOPin::Group2Io4 => Group::Two, + IOPin::Group3Io1 | IOPin::Group3Io2 | IOPin::Group3Io3 | IOPin::Group3Io4 => Group::Three, + IOPin::Group4Io1 | IOPin::Group4Io2 | IOPin::Group4Io3 | IOPin::Group4Io4 => Group::Four, + IOPin::Group5Io1 | IOPin::Group5Io2 | IOPin::Group5Io3 | IOPin::Group5Io4 => Group::Five, + IOPin::Group6Io1 | IOPin::Group6Io2 | IOPin::Group6Io3 | IOPin::Group6Io4 => Group::Six, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io1 | IOPin::Group7Io2 | IOPin::Group7Io3 | IOPin::Group7Io4 => Group::Seven, + #[cfg(tsc_v3)] + IOPin::Group8Io1 | IOPin::Group8Io2 | IOPin::Group8Io3 | IOPin::Group8Io4 => Group::Eight, + } + } + + /// Returns the `Group` associated with the given `IOPin`. + pub fn get_group(pin: IOPin) -> Group { + pin.group() + } +} + +impl BitOr for u32 { + type Output = u32; + fn bitor(self, rhs: IOPin) -> Self::Output { + let rhs: u32 = rhs.into(); + self | rhs + } +} + +impl BitOr for IOPin { + type Output = u32; + fn bitor(self, rhs: u32) -> Self::Output { + let val: u32 = self.into(); + val | rhs + } +} + +impl BitOr for IOPin { + type Output = u32; + fn bitor(self, rhs: Self) -> Self::Output { + let val: u32 = self.into(); + let rhs: u32 = rhs.into(); + val | rhs + } +} + +impl BitOrAssign for u32 { + fn bitor_assign(&mut self, rhs: IOPin) { + let rhs: u32 = rhs.into(); + *self |= rhs; + } +} + +impl BitAnd for u32 { + type Output = u32; + fn bitand(self, rhs: IOPin) -> Self::Output { + let rhs: u32 = rhs.into(); + self & rhs + } +} + +impl BitAnd for IOPin { + type Output = u32; + fn bitand(self, rhs: u32) -> Self::Output { + let val: u32 = self.into(); + val & rhs + } +} + +impl IOPin { + const fn to_u32(self) -> u32 { + match self { + IOPin::Group1Io1 => 0x00000001, + IOPin::Group1Io2 => 0x00000002, + IOPin::Group1Io3 => 0x00000004, + IOPin::Group1Io4 => 0x00000008, + IOPin::Group2Io1 => 0x00000010, + IOPin::Group2Io2 => 0x00000020, + IOPin::Group2Io3 => 0x00000040, + IOPin::Group2Io4 => 0x00000080, + IOPin::Group3Io1 => 0x00000100, + IOPin::Group3Io2 => 0x00000200, + IOPin::Group3Io3 => 0x00000400, + IOPin::Group3Io4 => 0x00000800, + IOPin::Group4Io1 => 0x00001000, + IOPin::Group4Io2 => 0x00002000, + IOPin::Group4Io3 => 0x00004000, + IOPin::Group4Io4 => 0x00008000, + IOPin::Group5Io1 => 0x00010000, + IOPin::Group5Io2 => 0x00020000, + IOPin::Group5Io3 => 0x00040000, + IOPin::Group5Io4 => 0x00080000, + IOPin::Group6Io1 => 0x00100000, + IOPin::Group6Io2 => 0x00200000, + IOPin::Group6Io3 => 0x00400000, + IOPin::Group6Io4 => 0x00800000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io1 => 0x01000000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io2 => 0x02000000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io3 => 0x04000000, + #[cfg(any(tsc_v2, tsc_v3))] + IOPin::Group7Io4 => 0x08000000, + #[cfg(tsc_v3)] + IOPin::Group8Io1 => 0x10000000, + #[cfg(tsc_v3)] + IOPin::Group8Io2 => 0x20000000, + #[cfg(tsc_v3)] + IOPin::Group8Io3 => 0x40000000, + #[cfg(tsc_v3)] + IOPin::Group8Io4 => 0x80000000, + } + } +} + +impl Into for IOPin { + fn into(self) -> u32 { + self.to_u32() + } +} diff --git a/embassy-stm32/src/tsc/mod.rs b/embassy-stm32/src/tsc/mod.rs index 17240e6bc..9359d83e9 100644 --- a/embassy-stm32/src/tsc/mod.rs +++ b/embassy-stm32/src/tsc/mod.rs @@ -1,90 +1,120 @@ //! TSC Peripheral Interface //! +//! This module provides an interface for the Touch Sensing Controller (TSC) peripheral. +//! It supports both blocking and async modes of operation, as well as different TSC versions (v1, v2, v3). +//! +//! # Key Concepts +//! +//! - **Pin Groups**: TSC pins are organized into groups, each containing up to four IOs. +//! - **Pin Roles**: Each pin in a group can have a role: Channel, Sample, or Shield. +//! - **Acquisition Banks**: Used for efficient, repeated TSC acquisitions on specific sets of pins. //! //! # Example (stm32) -//! ``` rust, ignore -//! -//! let mut device_config = embassy_stm32::Config::default(); -//! { -//! device_config.rcc.mux = ClockSrc::MSI(Msirange::RANGE_4MHZ); -//! } //! +//! ```rust +//! let device_config = embassy_stm32::Config::default(); //! let context = embassy_stm32::init(device_config); //! //! let config = tsc::Config { -//! ct_pulse_high_length: ChargeTransferPulseCycle::_2, -//! ct_pulse_low_length: ChargeTransferPulseCycle::_2, +//! ct_pulse_high_length: ChargeTransferPulseCycle::_4, +//! ct_pulse_low_length: ChargeTransferPulseCycle::_4, //! spread_spectrum: false, //! spread_spectrum_deviation: SSDeviation::new(2).unwrap(), //! spread_spectrum_prescaler: false, -//! pulse_generator_prescaler: PGPrescalerDivider::_4, -//! max_count_value: MaxCount::_8191, +//! pulse_generator_prescaler: PGPrescalerDivider::_16, +//! max_count_value: MaxCount::_255, //! io_default_mode: false, //! synchro_pin_polarity: false, //! acquisition_mode: false, //! max_count_interrupt: false, -//! channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3, -//! shield_ios: TscIOPin::Group1Io3.into(), -//! sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2, //! }; //! -//! let mut g1: PinGroup = PinGroup::new(); -//! g1.set_io2(context.PB13, PinType::Sample); -//! g1.set_io3(context.PB14, PinType::Shield); +//! let mut g2: PinGroupWithRoles = PinGroupWithRoles::new(); +//! g2.set_io1::(context.PB4); +//! let sensor_pin = g2.set_io2::(context.PB5); //! -//! let mut g2: PinGroup = PinGroup::new(); -//! g2.set_io1(context.PB4, PinType::Sample); -//! g2.set_io2(context.PB5, PinType::Channel); -//! -//! let mut g7: PinGroup = PinGroup::new(); -//! g7.set_io2(context.PE3, PinType::Sample); -//! g7.set_io3(context.PE4, PinType::Channel); +//! let pin_groups = PinGroups { +//! g2: Some(g2.pin_group), +//! ..Default::default() +//! }; //! //! let mut touch_controller = tsc::Tsc::new_blocking( //! context.TSC, -//! Some(g1), -//! Some(g2), -//! None, -//! None, -//! None, -//! None, -//! Some(g7), -//! None, +//! pin_groups, //! config, -//! ); +//! ).unwrap(); //! -//! touch_controller.discharge_io(true); -//! Timer::after_millis(1).await; +//! let discharge_delay = 5; // ms //! -//! touch_controller.start(); +//! loop { +//! touch_controller.set_active_channels_mask(sensor_pin.pin.into()); +//! touch_controller.start(); +//! touch_controller.poll_for_acquisition(); +//! touch_controller.discharge_io(true); +//! Timer::after_millis(discharge_delay).await; //! +//! match touch_controller.group_get_status(sensor_pin.pin.group()) { +//! GroupStatus::Complete => { +//! let group_val = touch_controller.group_get_value(sensor_pin.pin.group()); +//! // Process the touch value +//! // ... +//! } +//! GroupStatus::Ongoing => { +//! // Handle ongoing acquisition +//! // ... +//! } +//! } +//! } //! ``` +//! +//! # Async Usage +//! +//! For async operation, use `Tsc::new_async` and `pend_for_acquisition` instead of polling. #![macro_use] -/// Enums defined for peripheral parameters -pub mod enums; +/// Configuration structures and enums for the TSC peripheral. +pub mod config; + +/// Definitions and implementations for TSC pin groups. +pub mod pin_groups; + +/// Definitions and implementations for individual TSC I/O pins. +pub mod io_pin; + +/// Structures and implementations for TSC acquisition banks. +pub mod acquisition_banks; + +/// Core implementation of the TSC (Touch Sensing Controller) driver. +pub mod tsc; + +/// Type definitions used throughout the TSC module. +pub mod types; + +/// Error types and definitions for the TSC module. +pub mod errors; -use core::future::poll_fn; use core::marker::PhantomData; -use core::task::Poll; -use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use acquisition_banks::*; +pub use config::*; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; -pub use enums::*; +pub use errors::*; +pub use io_pin::*; +pub use pin_groups::*; +pub use tsc::*; +pub use types::*; -use crate::gpio::{AfType, AnyPin, OutputType, Speed}; -use crate::interrupt::typelevel::Interrupt; -use crate::mode::{Async, Blocking, Mode as PeriMode}; -use crate::rcc::{self, RccPeripheral}; -use crate::{interrupt, peripherals, Peripheral}; +use crate::rcc::RccPeripheral; +use crate::{interrupt, peripherals}; #[cfg(tsc_v1)] -const TSC_NUM_GROUPS: u32 = 6; +const TSC_NUM_GROUPS: usize = 6; #[cfg(tsc_v2)] -const TSC_NUM_GROUPS: u32 = 7; +const TSC_NUM_GROUPS: usize = 7; #[cfg(tsc_v3)] -const TSC_NUM_GROUPS: u32 = 8; +const TSC_NUM_GROUPS: usize = 8; /// Error type defined for TSC #[derive(Debug, Clone, Copy)] @@ -106,859 +136,6 @@ impl interrupt::typelevel::Handler for InterruptHandl } } -/// Pin type definition to control IO parameters -pub enum PinType { - /// Sensing channel pin connected to an electrode - Channel, - /// Sampling capacitor pin, one required for every pin group - Sample, - /// Shield pin connected to capacitive sensing shield - Shield, -} - -/// Peripheral state -#[derive(PartialEq, Clone, Copy)] -pub enum State { - /// Peripheral is being setup or reconfigured - Reset, - /// Ready to start acquisition - Ready, - /// In process of sensor acquisition - Busy, - /// Error occured during acquisition - Error, -} - -/// Individual group status checked after acquisition reported as complete -/// For groups with multiple channel pins, may take longer because acquisitions -/// are done sequentially. Check this status before pulling count for each -/// sampled channel -#[derive(PartialEq)] -pub enum GroupStatus { - /// Acquisition for channel still in progress - Ongoing, - /// Acquisition either not started or complete - Complete, -} - -/// Group identifier used to interrogate status -#[allow(missing_docs)] -pub enum Group { - One, - Two, - Three, - Four, - Five, - Six, - #[cfg(any(tsc_v2, tsc_v3))] - Seven, - #[cfg(tsc_v3)] - Eight, -} - -impl Into for Group { - fn into(self) -> usize { - match self { - Group::One => 0, - Group::Two => 1, - Group::Three => 2, - Group::Four => 3, - Group::Five => 4, - Group::Six => 5, - #[cfg(any(tsc_v2, tsc_v3))] - Group::Seven => 6, - #[cfg(tsc_v3)] - Group::Eight => 7, - } - } -} - -/// Peripheral configuration -#[derive(Clone, Copy)] -pub struct Config { - /// Duration of high state of the charge transfer pulse - pub ct_pulse_high_length: ChargeTransferPulseCycle, - /// Duration of the low state of the charge transfer pulse - pub ct_pulse_low_length: ChargeTransferPulseCycle, - /// Enable/disable of spread spectrum feature - pub spread_spectrum: bool, - /// Adds variable number of periods of the SS clk to pulse high state - pub spread_spectrum_deviation: SSDeviation, - /// Selects AHB clock divider used to generate SS clk - pub spread_spectrum_prescaler: bool, - /// Selects AHB clock divider used to generate pulse generator clk - pub pulse_generator_prescaler: PGPrescalerDivider, - /// Maximum number of charge transfer pulses that can be generated before error - pub max_count_value: MaxCount, - /// Defines config of all IOs when no ongoing acquisition - pub io_default_mode: bool, - /// Polarity of sync input pin - pub synchro_pin_polarity: bool, - /// Acquisition starts when start bit is set or with sync pin input - pub acquisition_mode: bool, - /// Enable max count interrupt - pub max_count_interrupt: bool, - /// Channel IO mask - pub channel_ios: u32, - /// Shield IO mask - pub shield_ios: u32, - /// Sampling IO mask - pub sampling_ios: u32, -} - -impl Default for Config { - fn default() -> Self { - Self { - ct_pulse_high_length: ChargeTransferPulseCycle::_1, - ct_pulse_low_length: ChargeTransferPulseCycle::_1, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(1).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_1, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: 0, - shield_ios: 0, - sampling_ios: 0, - } - } -} - -/// Pin struct that maintains usage -#[allow(missing_docs)] -pub struct TscPin<'d, T, C> { - _pin: PeripheralRef<'d, AnyPin>, - role: PinType, - phantom: PhantomData<(T, C)>, -} - -enum GroupError { - NoSample, - ChannelShield, -} - -/// Pin group definition -/// Pins are organized into groups of four IOs, all groups with a -/// sampling channel must also have a sampling capacitor channel. -#[allow(missing_docs)] -#[derive(Default)] -pub struct PinGroup<'d, T, C> { - d1: Option>, - d2: Option>, - d3: Option>, - d4: Option>, -} - -impl<'d, T: Instance, C> PinGroup<'d, T, C> { - /// Create new sensing group - pub fn new() -> Self { - Self { - d1: None, - d2: None, - d3: None, - d4: None, - } - } - - fn contains_shield(&self) -> bool { - let mut shield_count = 0; - - if let Some(pin) = &self.d1 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - if let Some(pin) = &self.d2 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - if let Some(pin) = &self.d3 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - if let Some(pin) = &self.d4 { - if let PinType::Shield = pin.role { - shield_count += 1; - } - } - - shield_count == 1 - } - - fn check_group(&self) -> Result<(), GroupError> { - let mut channel_count = 0; - let mut shield_count = 0; - let mut sample_count = 0; - if let Some(pin) = &self.d1 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - if let Some(pin) = &self.d2 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - if let Some(pin) = &self.d3 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - if let Some(pin) = &self.d4 { - match pin.role { - PinType::Channel => { - channel_count += 1; - } - PinType::Shield => { - shield_count += 1; - } - PinType::Sample => { - sample_count += 1; - } - } - } - - // Every group requires one sampling capacitor - if sample_count != 1 { - return Err(GroupError::NoSample); - } - - // Each group must have at least one shield or channel IO - if shield_count == 0 && channel_count == 0 { - return Err(GroupError::ChannelShield); - } - - // Any group can either contain channel ios or a shield IO - if shield_count != 0 && channel_count != 0 { - return Err(GroupError::ChannelShield); - } - - // No more than one shield IO is allow per group and amongst all groups - if shield_count > 1 { - return Err(GroupError::ChannelShield); - } - - Ok(()) - } -} - -macro_rules! group_impl { - ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => { - impl<'d, T: Instance> PinGroup<'d, T, $group> { - #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")] - pub fn set_io1(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d1 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - - #[doc = concat!("Create a new pin2 for ", stringify!($group), " TSC group instance.")] - pub fn set_io2(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d2 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - - #[doc = concat!("Create a new pin3 for ", stringify!($group), " TSC group instance.")] - pub fn set_io3(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d3 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - - #[doc = concat!("Create a new pin4 for ", stringify!($group), " TSC group instance.")] - pub fn set_io4(&mut self, pin: impl Peripheral

> + 'd, role: PinType) { - into_ref!(pin); - critical_section::with(|_| { - pin.set_low(); - pin.set_as_af( - pin.af_num(), - AfType::output( - match role { - PinType::Channel => OutputType::PushPull, - PinType::Sample => OutputType::OpenDrain, - PinType::Shield => OutputType::PushPull, - }, - Speed::VeryHigh, - ), - ); - self.d4 = Some(TscPin { - _pin: pin.map_into(), - role: role, - phantom: PhantomData, - }) - }) - } - } - }; -} - -group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin); -group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin); -group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin); -group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin); -group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin); -group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin); -group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin); -group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin); - -/// Group 1 marker type. -pub enum G1 {} -/// Group 2 marker type. -pub enum G2 {} -/// Group 3 marker type. -pub enum G3 {} -/// Group 4 marker type. -pub enum G4 {} -/// Group 5 marker type. -pub enum G5 {} -/// Group 6 marker type. -pub enum G6 {} -/// Group 7 marker type. -pub enum G7 {} -/// Group 8 marker type. -pub enum G8 {} - -/// TSC driver -pub struct Tsc<'d, T: Instance, K: PeriMode> { - _peri: PeripheralRef<'d, T>, - _g1: Option>, - _g2: Option>, - _g3: Option>, - _g4: Option>, - _g5: Option>, - _g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] - _g7: Option>, - #[cfg(tsc_v3)] - _g8: Option>, - state: State, - config: Config, - _kind: PhantomData, -} - -impl<'d, T: Instance> Tsc<'d, T, Async> { - /// Create a Tsc instance that can be awaited for completion - pub fn new_async( - peri: impl Peripheral

+ 'd, - g1: Option>, - g2: Option>, - g3: Option>, - g4: Option>, - g5: Option>, - g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: Option>, - #[cfg(tsc_v3)] g8: Option>, - config: Config, - _irq: impl interrupt::typelevel::Binding> + 'd, - ) -> Self { - // Need to check valid pin configuration input - let g1 = g1.filter(|b| b.check_group().is_ok()); - let g2 = g2.filter(|b| b.check_group().is_ok()); - let g3 = g3.filter(|b| b.check_group().is_ok()); - let g4 = g4.filter(|b| b.check_group().is_ok()); - let g5 = g5.filter(|b| b.check_group().is_ok()); - let g6 = g6.filter(|b| b.check_group().is_ok()); - #[cfg(any(tsc_v2, tsc_v3))] - let g7 = g7.filter(|b| b.check_group().is_ok()); - #[cfg(tsc_v3)] - let g8 = g8.filter(|b| b.check_group().is_ok()); - - match Self::check_shields( - &g1, - &g2, - &g3, - &g4, - &g5, - &g6, - #[cfg(any(tsc_v2, tsc_v3))] - &g7, - #[cfg(tsc_v3)] - &g8, - ) { - Ok(()) => Self::new_inner( - peri, - g1, - g2, - g3, - g4, - g5, - g6, - #[cfg(any(tsc_v2, tsc_v3))] - g7, - #[cfg(tsc_v3)] - g8, - config, - ), - Err(_) => Self::new_inner( - peri, - None, - None, - None, - None, - None, - None, - #[cfg(any(tsc_v2, tsc_v3))] - None, - #[cfg(tsc_v3)] - None, - config, - ), - } - } - /// Asyncronously wait for the end of an acquisition - pub async fn pend_for_acquisition(&mut self) { - poll_fn(|cx| match self.get_state() { - State::Busy => { - T::waker().register(cx.waker()); - T::regs().ier().write(|w| w.set_eoaie(true)); - if self.get_state() != State::Busy { - T::regs().ier().write(|w| w.set_eoaie(false)); - return Poll::Ready(()); - } - Poll::Pending - } - _ => { - T::regs().ier().write(|w| w.set_eoaie(false)); - Poll::Ready(()) - } - }) - .await; - } -} - -impl<'d, T: Instance> Tsc<'d, T, Blocking> { - /// Create a Tsc instance that must be polled for completion - pub fn new_blocking( - peri: impl Peripheral

+ 'd, - g1: Option>, - g2: Option>, - g3: Option>, - g4: Option>, - g5: Option>, - g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: Option>, - #[cfg(tsc_v3)] g8: Option>, - config: Config, - ) -> Self { - // Need to check valid pin configuration input - let g1 = g1.filter(|b| b.check_group().is_ok()); - let g2 = g2.filter(|b| b.check_group().is_ok()); - let g3 = g3.filter(|b| b.check_group().is_ok()); - let g4 = g4.filter(|b| b.check_group().is_ok()); - let g5 = g5.filter(|b| b.check_group().is_ok()); - let g6 = g6.filter(|b| b.check_group().is_ok()); - #[cfg(any(tsc_v2, tsc_v3))] - let g7 = g7.filter(|b| b.check_group().is_ok()); - #[cfg(tsc_v3)] - let g8 = g8.filter(|b| b.check_group().is_ok()); - - match Self::check_shields( - &g1, - &g2, - &g3, - &g4, - &g5, - &g6, - #[cfg(any(tsc_v2, tsc_v3))] - &g7, - #[cfg(tsc_v3)] - &g8, - ) { - Ok(()) => Self::new_inner( - peri, - g1, - g2, - g3, - g4, - g5, - g6, - #[cfg(any(tsc_v2, tsc_v3))] - g7, - #[cfg(tsc_v3)] - g8, - config, - ), - Err(_) => Self::new_inner( - peri, - None, - None, - None, - None, - None, - None, - #[cfg(any(tsc_v2, tsc_v3))] - None, - #[cfg(tsc_v3)] - None, - config, - ), - } - } - /// Wait for end of acquisition - pub fn poll_for_acquisition(&mut self) { - while self.get_state() == State::Busy {} - } -} - -impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { - /// Create new TSC driver - fn check_shields( - g1: &Option>, - g2: &Option>, - g3: &Option>, - g4: &Option>, - g5: &Option>, - g6: &Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: &Option>, - #[cfg(tsc_v3)] g8: &Option>, - ) -> Result<(), GroupError> { - let mut shield_count = 0; - - if let Some(pin_group) = g1 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g2 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g3 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g4 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g5 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - if let Some(pin_group) = g6 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - #[cfg(any(tsc_v2, tsc_v3))] - if let Some(pin_group) = g7 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - #[cfg(tsc_v3)] - if let Some(pin_group) = g8 { - if pin_group.contains_shield() { - shield_count += 1; - } - }; - - if shield_count > 1 { - return Err(GroupError::ChannelShield); - } - - Ok(()) - } - - fn extract_groups(io_mask: u32) -> u32 { - let mut groups: u32 = 0; - for idx in 0..TSC_NUM_GROUPS { - if io_mask & (0x0F << idx * 4) != 0 { - groups |= 1 << idx - } - } - groups - } - - fn new_inner( - peri: impl Peripheral

+ 'd, - g1: Option>, - g2: Option>, - g3: Option>, - g4: Option>, - g5: Option>, - g6: Option>, - #[cfg(any(tsc_v2, tsc_v3))] g7: Option>, - #[cfg(tsc_v3)] g8: Option>, - config: Config, - ) -> Self { - into_ref!(peri); - - rcc::enable_and_reset::(); - - T::regs().cr().modify(|w| { - w.set_tsce(true); - w.set_ctph(config.ct_pulse_high_length.into()); - w.set_ctpl(config.ct_pulse_low_length.into()); - w.set_sse(config.spread_spectrum); - // Prevent invalid configuration for pulse generator prescaler - if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1 - && (config.pulse_generator_prescaler == PGPrescalerDivider::_1 - || config.pulse_generator_prescaler == PGPrescalerDivider::_2) - { - w.set_pgpsc(PGPrescalerDivider::_4.into()); - } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2 - && config.pulse_generator_prescaler == PGPrescalerDivider::_1 - { - w.set_pgpsc(PGPrescalerDivider::_2.into()); - } else { - w.set_pgpsc(config.pulse_generator_prescaler.into()); - } - w.set_ssd(config.spread_spectrum_deviation.into()); - w.set_sspsc(config.spread_spectrum_prescaler); - - w.set_mcv(config.max_count_value.into()); - w.set_syncpol(config.synchro_pin_polarity); - w.set_am(config.acquisition_mode); - }); - - // Set IO configuration - // Disable Schmitt trigger hysteresis on all used TSC IOs - T::regs() - .iohcr() - .write(|w| w.0 = !(config.channel_ios | config.shield_ios | config.sampling_ios)); - - // Set channel and shield IOs - T::regs() - .ioccr() - .write(|w| w.0 = config.channel_ios | config.shield_ios); - - // Set sampling IOs - T::regs().ioscr().write(|w| w.0 = config.sampling_ios); - - // Set the groups to be acquired - T::regs() - .iogcsr() - .write(|w| w.0 = Self::extract_groups(config.channel_ios)); - - // Disable interrupts - T::regs().ier().modify(|w| { - w.set_eoaie(false); - w.set_mceie(false); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - unsafe { - T::Interrupt::enable(); - } - - Self { - _peri: peri, - _g1: g1, - _g2: g2, - _g3: g3, - _g4: g4, - _g5: g5, - _g6: g6, - #[cfg(any(tsc_v2, tsc_v3))] - _g7: g7, - #[cfg(tsc_v3)] - _g8: g8, - state: State::Ready, - config, - _kind: PhantomData, - } - } - - /// Start charge transfer acquisition - pub fn start(&mut self) { - self.state = State::Busy; - - // Disable interrupts - T::regs().ier().modify(|w| { - w.set_eoaie(false); - w.set_mceie(false); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - // Set the touch sensing IOs not acquired to the default mode - T::regs().cr().modify(|w| { - w.set_iodef(self.config.io_default_mode); - }); - - // Start the acquisition - T::regs().cr().modify(|w| { - w.set_start(true); - }); - } - - /// Stop charge transfer acquisition - pub fn stop(&mut self) { - T::regs().cr().modify(|w| { - w.set_start(false); - }); - - // Set the touch sensing IOs in low power mode - T::regs().cr().modify(|w| { - w.set_iodef(false); - }); - - // Clear flags - T::regs().icr().modify(|w| { - w.set_eoaic(true); - w.set_mceic(true); - }); - - self.state = State::Ready; - } - - /// Get current state of acquisition - pub fn get_state(&mut self) -> State { - if self.state == State::Busy { - if T::regs().isr().read().eoaf() { - if T::regs().isr().read().mcef() { - self.state = State::Error - } else { - self.state = State::Ready - } - } - } - self.state - } - - /// Get the individual group status to check acquisition complete - pub fn group_get_status(&mut self, index: Group) -> GroupStatus { - // Status bits are set by hardware when the acquisition on the corresponding - // enabled analog IO group is complete, cleared when new acquisition is started - let status = match index { - Group::One => T::regs().iogcsr().read().g1s(), - Group::Two => T::regs().iogcsr().read().g2s(), - Group::Three => T::regs().iogcsr().read().g3s(), - Group::Four => T::regs().iogcsr().read().g4s(), - Group::Five => T::regs().iogcsr().read().g5s(), - Group::Six => T::regs().iogcsr().read().g6s(), - #[cfg(any(tsc_v2, tsc_v3))] - Group::Seven => T::regs().iogcsr().read().g7s(), - #[cfg(tsc_v3)] - Group::Eight => T::regs().iogcsr().read().g8s(), - }; - match status { - true => GroupStatus::Complete, - false => GroupStatus::Ongoing, - } - } - - /// Get the count for the acquisiton, valid once group status is set - pub fn group_get_value(&mut self, index: Group) -> u16 { - T::regs().iogcr(index.into()).read().cnt() - } - - /// Discharge the IOs for subsequent acquisition - pub fn discharge_io(&mut self, status: bool) { - // Set the touch sensing IOs in low power mode - T::regs().cr().modify(|w| { - w.set_iodef(!status); - }); - } -} - -impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { - fn drop(&mut self) { - rcc::disable::(); - } -} - pub(crate) trait SealedInstance { fn regs() -> crate::pac::tsc::Tsc; fn waker() -> &'static AtomicWaker; @@ -966,7 +143,7 @@ pub(crate) trait SealedInstance { /// TSC instance trait #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral { +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral { /// Interrupt for this TSC instance type Interrupt: interrupt::typelevel::Interrupt; } @@ -988,36 +165,3 @@ foreach_interrupt!( } }; ); - -pin_trait!(G1IO1Pin, Instance); -pin_trait!(G1IO2Pin, Instance); -pin_trait!(G1IO3Pin, Instance); -pin_trait!(G1IO4Pin, Instance); -pin_trait!(G2IO1Pin, Instance); -pin_trait!(G2IO2Pin, Instance); -pin_trait!(G2IO3Pin, Instance); -pin_trait!(G2IO4Pin, Instance); -pin_trait!(G3IO1Pin, Instance); -pin_trait!(G3IO2Pin, Instance); -pin_trait!(G3IO3Pin, Instance); -pin_trait!(G3IO4Pin, Instance); -pin_trait!(G4IO1Pin, Instance); -pin_trait!(G4IO2Pin, Instance); -pin_trait!(G4IO3Pin, Instance); -pin_trait!(G4IO4Pin, Instance); -pin_trait!(G5IO1Pin, Instance); -pin_trait!(G5IO2Pin, Instance); -pin_trait!(G5IO3Pin, Instance); -pin_trait!(G5IO4Pin, Instance); -pin_trait!(G6IO1Pin, Instance); -pin_trait!(G6IO2Pin, Instance); -pin_trait!(G6IO3Pin, Instance); -pin_trait!(G6IO4Pin, Instance); -pin_trait!(G7IO1Pin, Instance); -pin_trait!(G7IO2Pin, Instance); -pin_trait!(G7IO3Pin, Instance); -pin_trait!(G7IO4Pin, Instance); -pin_trait!(G8IO1Pin, Instance); -pin_trait!(G8IO2Pin, Instance); -pin_trait!(G8IO3Pin, Instance); -pin_trait!(G8IO4Pin, Instance); diff --git a/embassy-stm32/src/tsc/pin_groups.rs b/embassy-stm32/src/tsc/pin_groups.rs new file mode 100644 index 000000000..6f914a94e --- /dev/null +++ b/embassy-stm32/src/tsc/pin_groups.rs @@ -0,0 +1,663 @@ +use core::marker::PhantomData; +use core::ops::BitOr; + +use super::errors::GroupError; +use super::io_pin::*; +use super::Instance; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::Peri; + +/// Pin type definition to control IO parameters +#[derive(PartialEq, Clone, Copy)] +pub enum PinType { + /// Sensing channel pin connected to an electrode + Channel, + /// Sampling capacitor pin, one required for every pin group + Sample, + /// Shield pin connected to capacitive sensing shield + Shield, +} + +/// Pin struct that maintains usage +#[allow(missing_docs)] +pub struct Pin<'d, T, Group> { + _pin: Peri<'d, AnyPin>, + role: PinType, + tsc_io_pin: IOPin, + phantom: PhantomData<(T, Group)>, +} + +impl<'d, T, Group> Pin<'d, T, Group> { + /// Returns the role of this TSC pin. + /// + /// The role indicates whether this pin is configured as a channel, + /// sampling capacitor, or shield in the TSC group. + /// + /// # Returns + /// The `PinType` representing the role of this pin. + pub fn role(&self) -> PinType { + self.role + } + + /// Returns the TSC IO pin associated with this pin. + /// + /// This method provides access to the specific TSC IO pin configuration, + /// which includes information about the pin's group and position within that group. + /// + /// # Returns + /// The `IOPin` representing this pin's TSC-specific configuration. + pub fn tsc_io_pin(&self) -> IOPin { + self.tsc_io_pin + } +} + +/// Represents a group of TSC (Touch Sensing Controller) pins. +/// +/// In the TSC peripheral, pins are organized into groups of four IOs. Each group +/// must have exactly one sampling capacitor pin and can have multiple channel pins +/// or a single shield pin. This structure encapsulates these pin configurations +/// for a single TSC group. +/// +/// # Pin Roles +/// - Sampling Capacitor: One required per group, used for charge transfer. +/// - Channel: Sensing pins connected to electrodes for touch detection. +/// - Shield: Optional, used for active shielding to improve sensitivity. +/// +/// # Constraints +/// - Each group must have exactly one sampling capacitor pin. +/// - A group can have either channel pins or a shield pin, but not both. +/// - No more than one shield pin is allowed across all groups. +#[allow(missing_docs)] +pub struct PinGroup<'d, T, Group> { + pin1: Option>, + pin2: Option>, + pin3: Option>, + pin4: Option>, +} + +impl<'d, T, G> Default for PinGroup<'d, T, G> { + fn default() -> Self { + Self { + pin1: None, + pin2: None, + pin3: None, + pin4: None, + } + } +} + +/// Defines roles and traits for TSC (Touch Sensing Controller) pins. +/// +/// This module contains marker types and traits that represent different roles +/// a TSC pin can have, such as channel, sample, or shield. +pub mod pin_roles { + use super::{OutputType, PinType}; + + /// Marker type for a TSC channel pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Channel; + + /// Marker type for a TSC sampling pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Sample; + + /// Marker type for a TSC shield pin. + #[derive(PartialEq, Clone, Copy, Debug)] + pub struct Shield; + + /// Trait for TSC pin roles. + /// + /// This trait defines the behavior and properties of different TSC pin roles. + /// It is implemented by the marker types `Channel`, `Sample`, and `Shield`. + pub trait Role { + /// Returns the `PinType` associated with this role. + fn pin_type() -> PinType; + + /// Returns the `OutputType` associated with this role. + fn output_type() -> OutputType; + } + + impl Role for Channel { + fn pin_type() -> PinType { + PinType::Channel + } + fn output_type() -> OutputType { + OutputType::PushPull + } + } + + impl Role for Sample { + fn pin_type() -> PinType { + PinType::Sample + } + fn output_type() -> OutputType { + OutputType::OpenDrain + } + } + + impl Role for Shield { + fn pin_type() -> PinType { + PinType::Shield + } + fn output_type() -> OutputType { + OutputType::PushPull + } + } +} + +/// Represents a group of TSC pins with their associated roles. +/// +/// This struct allows for type-safe configuration of TSC pin groups, +/// ensuring that pins are assigned appropriate roles within their group. +/// This type is essentially just a wrapper type around a `PinGroup` value. +/// +/// # Type Parameters +/// - `'d`: Lifetime of the pin group. +/// - `T`: The TSC instance type. +/// - `G`: The group identifier. +/// - `R1`, `R2`, `R3`, `R4`: Role types for each pin in the group, defaulting to `Channel`. +pub struct PinGroupWithRoles< + 'd, + T: Instance, + G, + R1 = pin_roles::Channel, + R2 = pin_roles::Channel, + R3 = pin_roles::Channel, + R4 = pin_roles::Channel, +> { + /// The underlying pin group without role information. + pub pin_group: PinGroup<'d, T, G>, + _phantom: PhantomData<(R1, R2, R3, R4)>, +} + +impl<'d, T: Instance, G, R1, R2, R3, R4> Default for PinGroupWithRoles<'d, T, G, R1, R2, R3, R4> { + fn default() -> Self { + Self { + pin_group: PinGroup::default(), + _phantom: PhantomData, + } + } +} + +impl<'d, T: Instance, G> PinGroup<'d, T, G> { + fn contains_exactly_one_shield_pin(&self) -> bool { + let shield_count = self.shield_pins().count(); + shield_count == 1 + } + + fn check_group(&self) -> Result<(), GroupError> { + let mut channel_count = 0; + let mut shield_count = 0; + let mut sample_count = 0; + for pin in self.pins().into_iter().flatten() { + match pin.role { + PinType::Channel => { + channel_count += 1; + } + PinType::Shield => { + shield_count += 1; + } + PinType::Sample => { + sample_count += 1; + } + } + } + + // Every group requires exactly one sampling capacitor + if sample_count != 1 { + return Err(GroupError::NoSamplingCapacitor); + } + + // Each group must have at least one shield or channel IO + if shield_count == 0 && channel_count == 0 { + return Err(GroupError::NoChannelOrShield); + } + + // Any group can either contain channel ios or a shield IO. + // (An active shield requires its own sampling capacitor) + if shield_count != 0 && channel_count != 0 { + return Err(GroupError::MixedChannelAndShield); + } + + // No more than one shield IO is allow per group and amongst all groups + if shield_count > 1 { + return Err(GroupError::MultipleShields); + } + + Ok(()) + } + + /// Returns a reference to the first pin in the group, if configured. + pub fn pin1(&self) -> Option<&Pin<'d, T, G>> { + self.pin1.as_ref() + } + + /// Returns a reference to the second pin in the group, if configured. + pub fn pin2(&self) -> Option<&Pin<'d, T, G>> { + self.pin2.as_ref() + } + + /// Returns a reference to the third pin in the group, if configured. + pub fn pin3(&self) -> Option<&Pin<'d, T, G>> { + self.pin3.as_ref() + } + + /// Returns a reference to the fourth pin in the group, if configured. + pub fn pin4(&self) -> Option<&Pin<'d, T, G>> { + self.pin4.as_ref() + } + + fn sample_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Sample) + } + + fn shield_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Shield) + } + + fn channel_pins(&self) -> impl Iterator + '_ { + self.pins_filtered(PinType::Channel) + } + + fn pins_filtered(&self, pin_type: PinType) -> impl Iterator + '_ { + self.pins().into_iter().filter_map(move |pin| { + pin.as_ref() + .and_then(|p| if p.role == pin_type { Some(p.tsc_io_pin) } else { None }) + }) + } + + fn make_channel_ios_mask(&self) -> u32 { + self.channel_pins().fold(0, u32::bitor) + } + + fn make_shield_ios_mask(&self) -> u32 { + self.shield_pins().fold(0, u32::bitor) + } + + fn make_sample_ios_mask(&self) -> u32 { + self.sample_pins().fold(0, u32::bitor) + } + + fn pins(&self) -> [&Option>; 4] { + [&self.pin1, &self.pin2, &self.pin3, &self.pin4] + } + + fn pins_mut(&mut self) -> [&mut Option>; 4] { + [&mut self.pin1, &mut self.pin2, &mut self.pin3, &mut self.pin4] + } +} + +#[cfg(any(tsc_v2, tsc_v3))] +macro_rules! TSC_V2_V3_GUARD { + ($e:expr) => {{ + #[cfg(any(tsc_v2, tsc_v3))] + { + $e + } + #[cfg(not(any(tsc_v2, tsc_v3)))] + { + compile_error!("Group 7 is not supported in this TSC version") + } + }}; +} + +#[cfg(tsc_v3)] +macro_rules! TSC_V3_GUARD { + ($e:expr) => {{ + #[cfg(tsc_v3)] + { + $e + } + #[cfg(not(tsc_v3))] + { + compile_error!("Group 8 is not supported in this TSC version") + } + }}; +} + +macro_rules! trait_to_io_pin { + (G1IO1Pin) => { + IOPin::Group1Io1 + }; + (G1IO2Pin) => { + IOPin::Group1Io2 + }; + (G1IO3Pin) => { + IOPin::Group1Io3 + }; + (G1IO4Pin) => { + IOPin::Group1Io4 + }; + + (G2IO1Pin) => { + IOPin::Group2Io1 + }; + (G2IO2Pin) => { + IOPin::Group2Io2 + }; + (G2IO3Pin) => { + IOPin::Group2Io3 + }; + (G2IO4Pin) => { + IOPin::Group2Io4 + }; + + (G3IO1Pin) => { + IOPin::Group3Io1 + }; + (G3IO2Pin) => { + IOPin::Group3Io2 + }; + (G3IO3Pin) => { + IOPin::Group3Io3 + }; + (G3IO4Pin) => { + IOPin::Group3Io4 + }; + + (G4IO1Pin) => { + IOPin::Group4Io1 + }; + (G4IO2Pin) => { + IOPin::Group4Io2 + }; + (G4IO3Pin) => { + IOPin::Group4Io3 + }; + (G4IO4Pin) => { + IOPin::Group4Io4 + }; + + (G5IO1Pin) => { + IOPin::Group5Io1 + }; + (G5IO2Pin) => { + IOPin::Group5Io2 + }; + (G5IO3Pin) => { + IOPin::Group5Io3 + }; + (G5IO4Pin) => { + IOPin::Group5Io4 + }; + + (G6IO1Pin) => { + IOPin::Group6Io1 + }; + (G6IO2Pin) => { + IOPin::Group6Io2 + }; + (G6IO3Pin) => { + IOPin::Group6Io3 + }; + (G6IO4Pin) => { + IOPin::Group6Io4 + }; + + (G7IO1Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io1) + }; + (G7IO2Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io2) + }; + (G7IO3Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io3) + }; + (G7IO4Pin) => { + TSC_V2_V3_GUARD!(IOPin::Group7Io4) + }; + + (G8IO1Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io1) + }; + (G8IO2Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io2) + }; + (G8IO3Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io3) + }; + (G8IO4Pin) => { + TSC_V3_GUARD!(IOPin::Group8Io4) + }; +} + +macro_rules! impl_set_io { + ($method:ident, $group:ident, $trait:ident, $index:expr) => { + #[doc = concat!("Create a new pin1 for ", stringify!($group), " TSC group instance.")] + pub fn $method(&mut self, pin: Peri<'d, impl $trait>) -> IOPinWithRole<$group, Role> { + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af(pin.af_num(), AfType::output(Role::output_type(), Speed::VeryHigh)); + let tsc_io_pin = trait_to_io_pin!($trait); + let new_pin = Pin { + _pin: pin.into(), + role: Role::pin_type(), + tsc_io_pin, + phantom: PhantomData, + }; + *self.pin_group.pins_mut()[$index] = Some(new_pin); + IOPinWithRole { + pin: tsc_io_pin, + phantom: PhantomData, + } + }) + } + }; +} + +macro_rules! group_impl { + ($group:ident, $trait1:ident, $trait2:ident, $trait3:ident, $trait4:ident) => { + impl<'d, T: Instance, R1: pin_roles::Role, R2: pin_roles::Role, R3: pin_roles::Role, R4: pin_roles::Role> + PinGroupWithRoles<'d, T, $group, R1, R2, R3, R4> + { + impl_set_io!(set_io1, $group, $trait1, 0); + impl_set_io!(set_io2, $group, $trait2, 1); + impl_set_io!(set_io3, $group, $trait3, 2); + impl_set_io!(set_io4, $group, $trait4, 3); + } + }; +} + +group_impl!(G1, G1IO1Pin, G1IO2Pin, G1IO3Pin, G1IO4Pin); +group_impl!(G2, G2IO1Pin, G2IO2Pin, G2IO3Pin, G2IO4Pin); +group_impl!(G3, G3IO1Pin, G3IO2Pin, G3IO3Pin, G3IO4Pin); +group_impl!(G4, G4IO1Pin, G4IO2Pin, G4IO3Pin, G4IO4Pin); +group_impl!(G5, G5IO1Pin, G5IO2Pin, G5IO3Pin, G5IO4Pin); +group_impl!(G6, G6IO1Pin, G6IO2Pin, G6IO3Pin, G6IO4Pin); +#[cfg(any(tsc_v2, tsc_v3))] +group_impl!(G7, G7IO1Pin, G7IO2Pin, G7IO3Pin, G7IO4Pin); +#[cfg(tsc_v3)] +group_impl!(G8, G8IO1Pin, G8IO2Pin, G8IO3Pin, G8IO4Pin); + +/// Group 1 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G1 {} +/// Group 2 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G2 {} +/// Group 3 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G3 {} +/// Group 4 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G4 {} +/// Group 5 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G5 {} +/// Group 6 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G6 {} +/// Group 7 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G7 {} +/// Group 8 marker type. +#[derive(Clone, Copy, Debug)] +pub enum G8 {} + +/// Represents the collection of pin groups for the Touch Sensing Controller (TSC). +/// +/// Each field corresponds to a specific group of TSC pins: +#[allow(missing_docs)] +pub struct PinGroups<'d, T: Instance> { + pub g1: Option>, + pub g2: Option>, + pub g3: Option>, + pub g4: Option>, + pub g5: Option>, + pub g6: Option>, + #[cfg(any(tsc_v2, tsc_v3))] + pub g7: Option>, + #[cfg(tsc_v3)] + pub g8: Option>, +} + +impl<'d, T: Instance> PinGroups<'d, T> { + pub(super) fn check(&self) -> Result<(), GroupError> { + let mut shield_count = 0; + + // Helper function to check a single group + fn check_group( + group: &Option>, + shield_count: &mut u32, + ) -> Result<(), GroupError> { + if let Some(group) = group { + group.check_group()?; + if group.contains_exactly_one_shield_pin() { + *shield_count += 1; + if *shield_count > 1 { + return Err(GroupError::MultipleShields); + } + } + } + Ok(()) + } + + // Check each group + check_group(&self.g1, &mut shield_count)?; + check_group(&self.g2, &mut shield_count)?; + check_group(&self.g3, &mut shield_count)?; + check_group(&self.g4, &mut shield_count)?; + check_group(&self.g5, &mut shield_count)?; + check_group(&self.g6, &mut shield_count)?; + #[cfg(any(tsc_v2, tsc_v3))] + check_group(&self.g7, &mut shield_count)?; + #[cfg(tsc_v3)] + check_group(&self.g8, &mut shield_count)?; + + Ok(()) + } + + pub(super) fn make_channel_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_channel_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_channel_ios_mask()); + } + mask + } + + pub(super) fn make_shield_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_shield_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_shield_ios_mask()); + } + mask + } + + pub(super) fn make_sample_ios_mask(&self) -> u32 { + #[allow(unused_mut)] + let mut mask = self.g1.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g2.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g3.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g4.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g5.as_ref().map_or(0, |g| g.make_sample_ios_mask()) + | self.g6.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + #[cfg(any(tsc_v2, tsc_v3))] + { + mask |= self.g7.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + } + #[cfg(tsc_v3)] + { + mask |= self.g8.as_ref().map_or(0, |g| g.make_sample_ios_mask()); + } + mask + } +} + +impl<'d, T: Instance> Default for PinGroups<'d, T> { + fn default() -> Self { + Self { + g1: None, + g2: None, + g3: None, + g4: None, + g5: None, + g6: None, + #[cfg(any(tsc_v2, tsc_v3))] + g7: None, + #[cfg(tsc_v3)] + g8: None, + } + } +} + +pin_trait!(G1IO1Pin, Instance); +pin_trait!(G1IO2Pin, Instance); +pin_trait!(G1IO3Pin, Instance); +pin_trait!(G1IO4Pin, Instance); + +pin_trait!(G2IO1Pin, Instance); +pin_trait!(G2IO2Pin, Instance); +pin_trait!(G2IO3Pin, Instance); +pin_trait!(G2IO4Pin, Instance); + +pin_trait!(G3IO1Pin, Instance); +pin_trait!(G3IO2Pin, Instance); +pin_trait!(G3IO3Pin, Instance); +pin_trait!(G3IO4Pin, Instance); + +pin_trait!(G4IO1Pin, Instance); +pin_trait!(G4IO2Pin, Instance); +pin_trait!(G4IO3Pin, Instance); +pin_trait!(G4IO4Pin, Instance); + +pin_trait!(G5IO1Pin, Instance); +pin_trait!(G5IO2Pin, Instance); +pin_trait!(G5IO3Pin, Instance); +pin_trait!(G5IO4Pin, Instance); + +pin_trait!(G6IO1Pin, Instance); +pin_trait!(G6IO2Pin, Instance); +pin_trait!(G6IO3Pin, Instance); +pin_trait!(G6IO4Pin, Instance); + +pin_trait!(G7IO1Pin, Instance); +pin_trait!(G7IO2Pin, Instance); +pin_trait!(G7IO3Pin, Instance); +pin_trait!(G7IO4Pin, Instance); + +pin_trait!(G8IO1Pin, Instance); +pin_trait!(G8IO2Pin, Instance); +pin_trait!(G8IO3Pin, Instance); +pin_trait!(G8IO4Pin, Instance); diff --git a/embassy-stm32/src/tsc/tsc.rs b/embassy-stm32/src/tsc/tsc.rs new file mode 100644 index 000000000..e92479c26 --- /dev/null +++ b/embassy-stm32/src/tsc/tsc.rs @@ -0,0 +1,446 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::BitOr; +use core::task::Poll; + +use embassy_hal_internal::Peri; + +use super::acquisition_banks::*; +use super::config::*; +use super::errors::*; +use super::io_pin::*; +use super::pin_groups::*; +use super::types::*; +use super::{Instance, InterruptHandler, TSC_NUM_GROUPS}; +use crate::interrupt::typelevel::Interrupt; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::{interrupt, rcc}; + +/// Internal structure holding masks for different types of TSC IOs. +/// +/// These masks are used during the initial configuration of the TSC peripheral +/// and for validating pin types during operations like creating acquisition banks. +struct IOMasks { + /// Mask representing all configured channel IOs + channel_ios: u32, + /// Mask representing all configured shield IOs + shield_ios: u32, + /// Mask representing all configured sampling IOs + sampling_ios: u32, +} + +/// TSC driver +pub struct Tsc<'d, T: Instance, K: PeriMode> { + _peri: Peri<'d, T>, + _pin_groups: PinGroups<'d, T>, + state: State, + config: Config, + masks: IOMasks, + _kind: PhantomData, +} + +impl<'d, T: Instance, K: PeriMode> Tsc<'d, T, K> { + // Helper method to check if a pin is a channel pin + fn is_channel_pin(&self, pin: IOPin) -> bool { + (self.masks.channel_ios & pin) != 0 + } + + /// Get the status of all groups involved in a AcquisitionBank + pub fn get_acquisition_bank_status(&self, bank: &AcquisitionBank) -> AcquisitionBankStatus { + let mut bank_status = AcquisitionBankStatus::default(); + for pin in bank.pins_iterator() { + let group = pin.group(); + let group_status = self.group_get_status(group); + let index: usize = group.into(); + bank_status.groups[index] = Some(group_status); + } + bank_status + } + + /// Get the values for all channels involved in a AcquisitionBank + pub fn get_acquisition_bank_values(&self, bank: &AcquisitionBank) -> AcquisitionBankReadings { + let mut bank_readings = AcquisitionBankReadings::default(); + for pin in bank.pins_iterator() { + let group = pin.group(); + let value = self.group_get_value(group); + let reading = ChannelReading { + sensor_value: value, + tsc_pin: pin, + }; + let index: usize = group.into(); + bank_readings.groups[index] = Some(reading); + } + bank_readings + } + + /// Creates a new TSC acquisition bank from the provided pin configuration. + /// + /// This method creates a `AcquisitionBank` that can be used for efficient, + /// repeated TSC acquisitions. It automatically generates the appropriate mask + /// for the provided pins. + /// + /// # Note on TSC Hardware Limitation + /// + /// The TSC hardware can only read one channel pin from each TSC group per acquisition. + /// + /// # Arguments + /// * `acquisition_bank_pins` - The pin configuration for the acquisition bank. + /// + /// # Returns + /// A new `AcquisitionBank` instance. + /// + /// # Example + /// + /// ``` + /// let tsc = // ... initialize TSC + /// let tsc_sensor1: tsc::IOPinWithRole = ...; + /// let tsc_sensor2: tsc::IOPinWithRole = ...; + /// + /// let bank = tsc.create_acquisition_bank(AcquisitionBankPins { + /// g1_pin: Some(tsc_sensor1), + /// g2_pin: Some(tsc_sensor2), + /// ..Default::default() + /// }); + /// + /// // Use the bank for acquisitions + /// tsc.set_active_channels_bank(&bank); + /// tsc.start(); + /// // ... perform acquisition ... + /// ``` + pub fn create_acquisition_bank(&self, acquisition_bank_pins: AcquisitionBankPins) -> AcquisitionBank { + let bank_mask = acquisition_bank_pins.iter().fold(0u32, BitOr::bitor); + + AcquisitionBank { + pins: acquisition_bank_pins, + mask: bank_mask, + } + } + + fn make_channels_mask(&self, channels: Itt) -> Result + where + Itt: IntoIterator, + { + let mut group_mask = 0u32; + let mut channel_mask = 0u32; + + for channel in channels { + if !self.is_channel_pin(channel) { + return Err(AcquisitionBankError::InvalidChannelPin); + } + + let group = channel.group(); + let group_bit: u32 = 1 << Into::::into(group); + if group_mask & group_bit != 0 { + return Err(AcquisitionBankError::MultipleChannelsPerGroup); + } + + group_mask |= group_bit; + channel_mask |= channel; + } + + Ok(channel_mask) + } + + /// Sets the active channels for the next TSC acquisition. + /// + /// This is a low-level method that directly sets the channel mask. For most use cases, + /// consider using `set_active_channels_bank` with a `AcquisitionBank` instead, which + /// provides a higher-level interface and additional safety checks. + /// + /// This method configures which sensor channels will be read during the next + /// touch sensing acquisition cycle. It should be called before starting a new + /// acquisition with the start() method. + /// + /// # Arguments + /// * `mask` - A 32-bit mask where each bit represents a channel. Set bits indicate + /// active channels. + /// + /// # Note + /// Only one pin from each TSC group can be read for each acquisition. This method + /// does not perform checks to ensure this limitation is met. Incorrect masks may + /// lead to unexpected behavior. + /// + /// # Safety + /// This method doesn't perform extensive checks on the provided mask. Ensure that + /// the mask is valid and adheres to hardware limitations to avoid undefined behavior. + pub fn set_active_channels_mask(&mut self, mask: u32) { + T::regs().ioccr().write(|w| w.0 = mask | self.masks.shield_ios); + } + + /// Convenience method for setting active channels directly from a slice of tsc::IOPin. + /// This method performs safety checks but is less efficient for repeated use. + pub fn set_active_channels(&mut self, channels: &[IOPin]) -> Result<(), AcquisitionBankError> { + let mask = self.make_channels_mask(channels.iter().cloned())?; + self.set_active_channels_mask(mask); + Ok(()) + } + + /// Sets the active channels for the next TSC acquisition using a pre-configured acquisition bank. + /// + /// This method efficiently configures the TSC peripheral to read the channels specified + /// in the provided `AcquisitionBank`. It's the recommended way to set up + /// channel configurations for acquisition, especially when using the same set of channels repeatedly. + /// + /// # Arguments + /// + /// * `bank` - A reference to a `AcquisitionBank` containing the pre-configured + /// TSC channel mask. + /// + /// # Example + /// + /// ``` + /// let tsc_sensor1: tsc::IOPinWithRole = ...; + /// let tsc_sensor2: tsc::IOPinWithRole = ...; + /// let mut touch_controller: Tsc<'_, TSC, Async> = ...; + /// let bank = touch_controller.create_acquisition_bank(AcquisitionBankPins { + /// g1_pin: Some(tsc_sensor1), + /// g2_pin: Some(tsc_sensor2), + /// ..Default::default() + /// }); + /// + /// touch_controller.set_active_channels_bank(&bank); + /// touch_controller.start(); + /// // ... perform acquisition ... + /// ``` + /// + /// This method should be called before starting a new acquisition with the `start()` method. + pub fn set_active_channels_bank(&mut self, bank: &AcquisitionBank) { + self.set_active_channels_mask(bank.mask) + } + + fn extract_groups(io_mask: u32) -> u32 { + let mut groups: u32 = 0; + for idx in 0..TSC_NUM_GROUPS { + if io_mask & (0x0F << (idx * 4)) != 0 { + groups |= 1 << idx + } + } + groups + } + + fn new_inner(peri: Peri<'d, T>, pin_groups: PinGroups<'d, T>, config: Config) -> Result { + pin_groups.check()?; + + let masks = IOMasks { + channel_ios: pin_groups.make_channel_ios_mask(), + shield_ios: pin_groups.make_shield_ios_mask(), + sampling_ios: pin_groups.make_sample_ios_mask(), + }; + + rcc::enable_and_reset::(); + + T::regs().cr().modify(|w| { + w.set_tsce(true); + w.set_ctph(config.ct_pulse_high_length.into()); + w.set_ctpl(config.ct_pulse_low_length.into()); + w.set_sse(config.spread_spectrum); + // Prevent invalid configuration for pulse generator prescaler + if config.ct_pulse_low_length == ChargeTransferPulseCycle::_1 + && (config.pulse_generator_prescaler == PGPrescalerDivider::_1 + || config.pulse_generator_prescaler == PGPrescalerDivider::_2) + { + w.set_pgpsc(PGPrescalerDivider::_4.into()); + } else if config.ct_pulse_low_length == ChargeTransferPulseCycle::_2 + && config.pulse_generator_prescaler == PGPrescalerDivider::_1 + { + w.set_pgpsc(PGPrescalerDivider::_2.into()); + } else { + w.set_pgpsc(config.pulse_generator_prescaler.into()); + } + w.set_ssd(config.spread_spectrum_deviation.into()); + w.set_sspsc(config.spread_spectrum_prescaler); + + w.set_mcv(config.max_count_value.into()); + w.set_syncpol(config.synchro_pin_polarity); + w.set_am(config.acquisition_mode); + }); + + // Set IO configuration + // Disable Schmitt trigger hysteresis on all used TSC IOs + T::regs() + .iohcr() + .write(|w| w.0 = !(masks.channel_ios | masks.shield_ios | masks.sampling_ios)); + + // Set channel and shield IOs + T::regs().ioccr().write(|w| w.0 = masks.channel_ios | masks.shield_ios); + + // Set sampling IOs + T::regs().ioscr().write(|w| w.0 = masks.sampling_ios); + + // Set the groups to be acquired + // Lower bits of `iogcsr` are for enabling groups, while the higher bits are for reading + // status of acquisiton for a group, see method `Tsc::group_get_status`. + T::regs() + .iogcsr() + .write(|w| w.0 = Self::extract_groups(masks.channel_ios)); + + // Disable interrupts + T::regs().ier().modify(|w| { + w.set_eoaie(false); + w.set_mceie(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + unsafe { + T::Interrupt::enable(); + } + + Ok(Self { + _peri: peri, + _pin_groups: pin_groups, + state: State::Ready, + config, + masks, + _kind: PhantomData, + }) + } + + /// Start charge transfer acquisition + pub fn start(&mut self) { + self.state = State::Busy; + + // Disable interrupts + T::regs().ier().modify(|w| { + w.set_eoaie(false); + w.set_mceie(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + // Set the touch sensing IOs not acquired to the default mode + T::regs().cr().modify(|w| { + w.set_iodef(self.config.io_default_mode); + }); + + // Start the acquisition + T::regs().cr().modify(|w| { + w.set_start(true); + }); + } + + /// Stop charge transfer acquisition + pub fn stop(&mut self) { + T::regs().cr().modify(|w| { + w.set_start(false); + }); + + // Set the touch sensing IOs in low power mode + T::regs().cr().modify(|w| { + w.set_iodef(false); + }); + + // Clear flags + T::regs().icr().modify(|w| { + w.set_eoaic(true); + w.set_mceic(true); + }); + + self.state = State::Ready; + } + + /// Get current state of acquisition + pub fn get_state(&mut self) -> State { + if self.state == State::Busy && T::regs().isr().read().eoaf() { + if T::regs().isr().read().mcef() { + self.state = State::Error + } else { + self.state = State::Ready + } + } + self.state + } + + /// Get the individual group status to check acquisition complete + pub fn group_get_status(&self, index: Group) -> GroupStatus { + // Status bits are set by hardware when the acquisition on the corresponding + // enabled analog IO group is complete, cleared when new acquisition is started + let status = match index { + Group::One => T::regs().iogcsr().read().g1s(), + Group::Two => T::regs().iogcsr().read().g2s(), + Group::Three => T::regs().iogcsr().read().g3s(), + Group::Four => T::regs().iogcsr().read().g4s(), + Group::Five => T::regs().iogcsr().read().g5s(), + Group::Six => T::regs().iogcsr().read().g6s(), + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => T::regs().iogcsr().read().g7s(), + #[cfg(tsc_v3)] + Group::Eight => T::regs().iogcsr().read().g8s(), + }; + match status { + true => GroupStatus::Complete, + false => GroupStatus::Ongoing, + } + } + + /// Get the count for the acquisiton, valid once group status is set + pub fn group_get_value(&self, index: Group) -> u16 { + T::regs().iogcr(index.into()).read().cnt() + } + + /// Discharge the IOs for subsequent acquisition + pub fn discharge_io(&mut self, status: bool) { + // Set the touch sensing IOs in low power mode + T::regs().cr().modify(|w| { + w.set_iodef(!status); + }); + } +} + +impl<'d, T: Instance, K: PeriMode> Drop for Tsc<'d, T, K> { + fn drop(&mut self) { + rcc::disable::(); + } +} + +impl<'d, T: Instance> Tsc<'d, T, Async> { + /// Create a Tsc instance that can be awaited for completion + pub fn new_async( + peri: Peri<'d, T>, + pin_groups: PinGroups<'d, T>, + config: Config, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Result { + Self::new_inner(peri, pin_groups, config) + } + + /// Asyncronously wait for the end of an acquisition + pub async fn pend_for_acquisition(&mut self) { + poll_fn(|cx| match self.get_state() { + State::Busy => { + T::waker().register(cx.waker()); + T::regs().ier().write(|w| w.set_eoaie(true)); + if self.get_state() != State::Busy { + T::regs().ier().write(|w| w.set_eoaie(false)); + return Poll::Ready(()); + } + Poll::Pending + } + _ => { + T::regs().ier().write(|w| w.set_eoaie(false)); + Poll::Ready(()) + } + }) + .await; + } +} + +impl<'d, T: Instance> Tsc<'d, T, Blocking> { + /// Create a Tsc instance that must be polled for completion + pub fn new_blocking(peri: Peri<'d, T>, pin_groups: PinGroups<'d, T>, config: Config) -> Result { + Self::new_inner(peri, pin_groups, config) + } + + /// Wait for end of acquisition + pub fn poll_for_acquisition(&mut self) { + while self.get_state() == State::Busy {} + } +} diff --git a/embassy-stm32/src/tsc/types.rs b/embassy-stm32/src/tsc/types.rs new file mode 100644 index 000000000..0e8fa7f28 --- /dev/null +++ b/embassy-stm32/src/tsc/types.rs @@ -0,0 +1,93 @@ +/// Peripheral state +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy)] +pub enum State { + /// Peripheral is being setup or reconfigured + Reset, + /// Ready to start acquisition + Ready, + /// In process of sensor acquisition + Busy, + /// Error occured during acquisition + Error, +} + +/// Individual group status checked after acquisition reported as complete +/// For groups with multiple channel pins, may take longer because acquisitions +/// are done sequentially. Check this status before pulling count for each +/// sampled channel +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(PartialEq, Clone, Copy)] +pub enum GroupStatus { + /// Acquisition for channel still in progress + Ongoing, + /// Acquisition either not started or complete + Complete, +} + +/// Group identifier used to interrogate status +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(missing_docs)] +#[derive(PartialEq, Clone, Copy)] +pub enum Group { + One, + Two, + Three, + Four, + Five, + Six, + #[cfg(any(tsc_v2, tsc_v3))] + Seven, + #[cfg(tsc_v3)] + Eight, +} + +impl Into for Group { + fn into(self) -> usize { + match self { + Group::One => 0, + Group::Two => 1, + Group::Three => 2, + Group::Four => 3, + Group::Five => 4, + Group::Six => 5, + #[cfg(any(tsc_v2, tsc_v3))] + Group::Seven => 6, + #[cfg(tsc_v3)] + Group::Eight => 7, + } + } +} + +/// Error returned when attempting to create a Group from an invalid numeric value. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InvalidGroupError { + invalid_value: usize, +} + +impl InvalidGroupError { + #[allow(missing_docs)] + pub fn new(value: usize) -> Self { + Self { invalid_value: value } + } +} + +impl TryFrom for Group { + type Error = InvalidGroupError; + + fn try_from(value: usize) -> Result { + match value { + 0 => Ok(Group::One), + 1 => Ok(Group::Two), + 2 => Ok(Group::Three), + 3 => Ok(Group::Four), + 4 => Ok(Group::Five), + 5 => Ok(Group::Six), + #[cfg(any(tsc_v2, tsc_v3))] + 6 => Ok(Group::Two), + #[cfg(tsc_v3)] + 7 => Ok(Group::Two), + n => Err(InvalidGroupError::new(n)), + } + } +} diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 40aea75cb..87693f148 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -20,15 +20,15 @@ use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::{into_ref, Peripheral}; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use crate::dma::{ChannelAndRequest, TransferOptions}; -use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; -pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; +pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, Rxordset, TypecVstateCc as CcVState}; use crate::rcc::{self, RccPeripheral}; +use crate::{interrupt, Peri}; pub(crate) fn init( _cs: critical_section::CriticalSection, @@ -86,6 +86,34 @@ pub enum CcPull { Source3_0A, } +/// UCPD configuration +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Receive SOP packets + pub sop: bool, + /// Receive SOP' packets + pub sop_prime: bool, + /// Receive SOP'' packets + pub sop_double_prime: bool, + /// Receive SOP'_Debug packets + pub sop_prime_debug: bool, + /// Receive SOP''_Debug packets + pub sop_double_prime_debug: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + sop: true, + sop_prime: false, + sop_double_prime: false, + sop_prime_debug: false, + sop_double_prime_debug: false, + } + } +} + /// UCPD driver. pub struct Ucpd<'d, T: Instance> { cc_phy: CcPhy<'d, T>, @@ -94,12 +122,12 @@ pub struct Ucpd<'d, T: Instance> { impl<'d, T: Instance> Ucpd<'d, T> { /// Creates a new UCPD driver instance. pub fn new( - _peri: impl Peripheral

+ 'd, + _peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - cc1: impl Peripheral

> + 'd, - cc2: impl Peripheral

> + 'd, + cc1: Peri<'d, impl Cc1Pin>, + cc2: Peri<'d, impl Cc2Pin>, + config: Config, ) -> Self { - into_ref!(cc1, cc2); cc1.set_as_analog(); cc2.set_as_analog(); @@ -108,6 +136,13 @@ impl<'d, T: Instance> Ucpd<'d, T> { unsafe { T::Interrupt::enable() }; let r = T::REGS; + + #[cfg(stm32h5)] + r.cfgr2().write(|w| { + // Only takes effect, when UCPDEN=0. + w.set_rxafilten(true); + }); + r.cfgr1().write(|w| { // "The receiver is designed to work in the clock frequency range from 6 to 18 MHz. // However, the optimum performance is ensured in the range from 6 to 12 MHz" @@ -129,9 +164,15 @@ impl<'d, T: Instance> Ucpd<'d, T> { // 1.75us * 17 = ~30us w.set_ifrgap(17 - 1); - // TODO: Currently only hard reset and SOP messages can be received. // UNDOCUMENTED: This register can only be written while UCPDEN=0 (found by testing). - w.set_rxordseten(0b1001); + let rxordset = (config.sop as u16) << 0 + | (config.sop_prime as u16) << 1 + | (config.sop_double_prime as u16) << 2 + // Hard reset + | 0x1 << 3 + | (config.sop_prime_debug as u16) << 4 + | (config.sop_double_prime_debug as u16) << 5; + w.set_rxordseten(rxordset); // Enable DMA w.set_txdmaen(true); @@ -140,6 +181,18 @@ impl<'d, T: Instance> Ucpd<'d, T> { w.set_ucpden(true); }); + // Software trim according to RM0481, p. 2650/2668 + #[cfg(stm32h5)] + { + let trim_rd_cc1 = unsafe { *(0x4002_242C as *const u32) & 0xF }; + let trim_rd_cc2 = unsafe { ((*(0x4002_242C as *const u32)) >> 8) & 0xF }; + + r.cfgr3().write(|w| { + w.set_trim_cc1_rd(trim_rd_cc1 as u8); + w.set_trim_cc2_rd(trim_rd_cc2 as u8); + }); + } + Self { cc_phy: CcPhy { _lifetime: PhantomData }, } @@ -154,8 +207,8 @@ impl<'d, T: Instance> Ucpd<'d, T> { /// and a Power Delivery (PD) PHY with receiver and transmitter. pub fn split_pd_phy( self, - rx_dma: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, + rx_dma: Peri<'d, impl RxDma>, + tx_dma: Peri<'d, impl TxDma>, cc_sel: CcSel, ) -> (CcPhy<'d, T>, PdPhy<'d, T>) { let r = T::REGS; @@ -175,7 +228,6 @@ impl<'d, T: Instance> Ucpd<'d, T> { // Both parts must be dropped before the peripheral can be disabled. T::state().drop_not_ready.store(true, Ordering::Relaxed); - into_ref!(rx_dma, tx_dma); let rx_dma_req = rx_dma.request(); let tx_dma_req = tx_dma.request(); ( @@ -183,11 +235,11 @@ impl<'d, T: Instance> Ucpd<'d, T> { PdPhy { _lifetime: PhantomData, rx_dma: ChannelAndRequest { - channel: rx_dma.map_into(), + channel: rx_dma.into(), request: rx_dma_req, }, tx_dma: ChannelAndRequest { - channel: tx_dma.map_into(), + channel: tx_dma.into(), request: tx_dma_req, }, }, @@ -212,7 +264,7 @@ impl<'d, T: Instance> Drop for CcPhy<'d, T> { // Check if the PdPhy part was dropped already. let drop_not_ready = &T::state().drop_not_ready; if drop_not_ready.load(Ordering::Relaxed) { - drop_not_ready.store(true, Ordering::Relaxed); + drop_not_ready.store(false, Ordering::Relaxed); } else { r.cfgr1().write(|w| w.set_ucpden(false)); rcc::disable::(); @@ -243,6 +295,25 @@ impl<'d, T: Instance> CcPhy<'d, T> { }); }); + // Software trim according to RM0481, p. 2650/2668 + #[cfg(stm32h5)] + T::REGS.cfgr3().modify(|w| match cc_pull { + CcPull::Source1_5A => { + let trim_1a5_cc1 = unsafe { *(0x08FF_F844 as *const u32) & 0xF }; + let trim_1a5_cc2 = unsafe { ((*(0x08FF_F844 as *const u32)) >> 16) & 0xF }; + + w.set_trim_cc1_rp(trim_1a5_cc1 as u8); + w.set_trim_cc2_rp(trim_1a5_cc2 as u8); + } + _ => { + let trim_3a0_cc1 = unsafe { (*(0x4002_242C as *const u32) >> 4) & 0xF }; + let trim_3a0_cc2 = unsafe { ((*(0x4002_242C as *const u32)) >> 12) & 0xF }; + + w.set_trim_cc1_rp(trim_3a0_cc1 as u8); + w.set_trim_cc2_rp(trim_3a0_cc2 as u8); + } + }); + // Disable dead-battery pull-down resistors which are enabled by default on boot. critical_section::with(|cs| { init( @@ -288,6 +359,22 @@ impl<'d, T: Instance> CcPhy<'d, T> { } } +/// Receive SOP. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Sop { + /// SOP + Sop, + /// SOP' + SopPrime, + /// SOP'' + SopDoublePrime, + /// SOP'_Debug + SopPrimeDebug, + /// SOP''_Debug + SopDoublePrimeDebug, +} + /// Receive Error. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -322,13 +409,14 @@ pub struct PdPhy<'d, T: Instance> { impl<'d, T: Instance> Drop for PdPhy<'d, T> { fn drop(&mut self) { - T::REGS.cr().modify(|w| w.set_phyrxen(false)); - // Check if the Type-C part was dropped already. + let r = T::REGS; + r.cr().modify(|w| w.set_phyrxen(false)); + // Check if the CcPhy part was dropped already. let drop_not_ready = &T::state().drop_not_ready; if drop_not_ready.load(Ordering::Relaxed) { - drop_not_ready.store(true, Ordering::Relaxed); + drop_not_ready.store(false, Ordering::Relaxed); } else { - T::REGS.cfgr1().write(|w| w.set_ucpden(false)); + r.cfgr1().write(|w| w.set_ucpden(false)); rcc::disable::(); T::Interrupt::disable(); } @@ -340,9 +428,16 @@ impl<'d, T: Instance> PdPhy<'d, T> { /// /// Returns the number of received bytes or an error. pub async fn receive(&mut self, buf: &mut [u8]) -> Result { + self.receive_with_sop(buf).await.map(|(_sop, size)| size) + } + + /// Receives SOP and a PD message into the provided buffer. + /// + /// Returns the start of packet type and number of received bytes or an error. + pub async fn receive_with_sop(&mut self, buf: &mut [u8]) -> Result<(Sop, usize), RxError> { let r = T::REGS; - let dma = unsafe { + let mut dma = unsafe { self.rx_dma .read(r.rxdr().as_ptr() as *mut u8, buf, TransferOptions::default()) }; @@ -357,14 +452,24 @@ impl<'d, T: Instance> PdPhy<'d, T> { }); }); + let mut rxpaysz = 0; + + // Stop DMA reception immediately after receiving a packet, to prevent storing multiple packets in the same buffer. poll_fn(|cx| { let sr = r.sr().read(); + if sr.rxhrstdet() { + dma.request_stop(); + // Clean and re-enable hard reset receive interrupt. r.icr().write(|w| w.set_rxhrstdetcf(true)); r.imr().modify(|w| w.set_rxhrstdetie(true)); Poll::Ready(Err(RxError::HardReset)) } else if sr.rxmsgend() { + dma.request_stop(); + // Should be read immediately on interrupt. + rxpaysz = r.rx_payszr().read().rxpaysz().into(); + let ret = if sr.rxovr() { Err(RxError::Overrun) } else if sr.rxerr() { @@ -388,7 +493,18 @@ impl<'d, T: Instance> PdPhy<'d, T> { } } - Ok(r.rx_payszr().read().rxpaysz().into()) + let sop = match r.rx_ordsetr().read().rxordset() { + Rxordset::SOP => Sop::Sop, + Rxordset::SOP_PRIME => Sop::SopPrime, + Rxordset::SOP_DOUBLE_PRIME => Sop::SopDoublePrime, + Rxordset::SOP_PRIME_DEBUG => Sop::SopPrimeDebug, + Rxordset::SOP_DOUBLE_PRIME_DEBUG => Sop::SopDoublePrimeDebug, + Rxordset::CABLE_RESET => return Err(RxError::HardReset), + // Extension headers are not supported + _ => unreachable!(), + }; + + Ok((sop, rxpaysz)) } fn enable_rx_interrupt(enable: bool) { @@ -571,7 +687,7 @@ trait SealedInstance { /// UCPD instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral { +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral { /// Interrupt for this instance. type Interrupt: crate::interrupt::typelevel::Interrupt; } diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 06cc0e41d..73ab46404 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -6,16 +6,17 @@ use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; -use embassy_hal_internal::{Peripheral, PeripheralRef}; +use embassy_hal_internal::Peri; use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(any(usart_v1, usart_v2)))] use super::DePin; use super::{ - clear_interrupt_flags, configure, rdr, reconfigure, sr, tdr, Config, ConfigError, CtsPin, Error, Info, Instance, - Regs, RtsPin, RxPin, TxPin, + clear_interrupt_flags, configure, half_duplex_set_rx_tx_before_write, rdr, reconfigure, send_break, set_baudrate, + sr, tdr, Config, ConfigError, CtsPin, Duplex, Error, HalfDuplexReadback, Info, Instance, Regs, RtsPin, RxPin, + TxPin, }; -use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::gpio::{AfType, AnyPin, Pull, SealedPin as _}; use crate::interrupt::{self, InterruptExt}; use crate::time::Hertz; @@ -108,6 +109,8 @@ unsafe fn on_interrupt(r: Regs, state: &'static State) { }); } + half_duplex_set_rx_tx_before_write(&r, state.half_duplex_readback.load(Ordering::Relaxed)); + tdr(r).write_volatile(buf[0].into()); tx_reader.pop_done(1); } else { @@ -126,6 +129,7 @@ pub(super) struct State { tx_buf: RingBuffer, tx_done: AtomicBool, tx_rx_refcount: AtomicU8, + half_duplex_readback: AtomicBool, } impl State { @@ -137,6 +141,7 @@ impl State { tx_waker: AtomicWaker::new(), tx_done: AtomicBool::new(true), tx_rx_refcount: AtomicU8::new(0), + half_duplex_readback: AtomicBool::new(false), } } } @@ -154,9 +159,10 @@ pub struct BufferedUartTx<'d> { info: &'static Info, state: &'static State, kernel_clock: Hertz, - tx: Option>, - cts: Option>, - de: Option>, + tx: Option>, + cts: Option>, + de: Option>, + is_borrowed: bool, } /// Rx-only buffered UART @@ -166,8 +172,9 @@ pub struct BufferedUartRx<'d> { info: &'static Info, state: &'static State, kernel_clock: Hertz, - rx: Option>, - rts: Option>, + rx: Option>, + rts: Option>, + is_borrowed: bool, } impl<'d> SetConfig for BufferedUart<'d> { @@ -200,18 +207,18 @@ impl<'d> SetConfig for BufferedUartTx<'d> { impl<'d> BufferedUart<'d> { /// Create a new bidirectional buffered UART driver pub fn new( - peri: impl Peripheral

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

> + 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], + _irq: impl interrupt::typelevel::Binding> + 'd, config: Config, ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), None, None, None, @@ -223,22 +230,22 @@ impl<'d> BufferedUart<'d> { /// Create a new bidirectional buffered UART driver with request-to-send and clear-to-send pins pub fn new_with_rtscts( - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(cts, AfType::input(Pull::None)), + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + new_pin!(rts, config.rts_config.af_type()), + new_pin!(cts, AfType::input(config.cts_pull)), None, tx_buffer, rx_buffer, @@ -246,25 +253,149 @@ impl<'d> BufferedUart<'d> { ) } + /// Create a new bidirectional buffered UART driver with only the RTS pin as the DE pin + pub fn new_with_rts_as_de( + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + rts: Peri<'d, impl RtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + None, + None, + new_pin!(rts, AfType::input(Pull::None)), // RTS mapped used as DE + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a new bidirectional buffered UART driver with only the request-to-send pin + pub fn new_with_rts( + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + rts: Peri<'d, impl RtsPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Result { + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + new_pin!(rts, AfType::input(Pull::None)), + None, // no CTS + None, // no DE + tx_buffer, + rx_buffer, + config, + ) + } + /// Create a new bidirectional buffered UART driver with a driver-enable pin #[cfg(not(any(usart_v1, usart_v2)))] pub fn new_with_de( - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + de: Peri<'d, impl DePin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, - de: impl Peripheral

> + 'd, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, config.rx_af()), + new_pin!(tx, config.tx_af()), + None, + None, + new_pin!(de, config.de_config.af_type()), + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a single-wire half-duplex Uart transceiver on a single Tx pin. + /// + /// See [`new_half_duplex_on_rx`][`Self::new_half_duplex_on_rx`] if you would prefer to use an Rx pin + /// (when it is available for your chip). There is no functional difference between these methods, as both + /// allow bidirectional communication. + /// + /// The TX pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. It means that the I/O must be configured so that TX is + /// configured as alternate function open-drain with an external pull-up + /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict + /// on the line must be managed by software (for instance by using a centralized arbiter). + #[doc(alias("HDSEL"))] + pub fn new_half_duplex( + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + mut config: Config, + readback: HalfDuplexReadback, + ) -> Result { + #[cfg(not(any(usart_v1, usart_v2)))] + { + config.swap_rx_tx = false; + } + config.duplex = Duplex::Half(readback); + + Self::new_inner( + peri, + None, + new_pin!(tx, config.tx_af()), + None, + None, + None, + tx_buffer, + rx_buffer, + config, + ) + } + + /// Create a single-wire half-duplex Uart transceiver on a single Rx pin. + /// + /// See [`new_half_duplex`][`Self::new_half_duplex`] if you would prefer to use an Tx pin. + /// There is no functional difference between these methods, as both allow bidirectional communication. + /// + /// The pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. + /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict + /// on the line must be managed by software (for instance by using a centralized arbiter). + #[cfg(not(any(usart_v1, usart_v2)))] + #[doc(alias("HDSEL"))] + pub fn new_half_duplex_on_rx( + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + _irq: impl interrupt::typelevel::Binding> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + mut config: Config, + readback: HalfDuplexReadback, + ) -> Result { + config.swap_rx_tx = true; + config.duplex = Duplex::Half(readback); + + Self::new_inner( + peri, + new_pin!(rx, config.rx_af()), + None, + None, None, None, - new_pin!(de, AfType::output(OutputType::PushPull, Speed::Medium)), tx_buffer, rx_buffer, config, @@ -272,12 +403,12 @@ impl<'d> BufferedUart<'d> { } fn new_inner( - _peri: impl Peripheral

+ 'd, - rx: Option>, - tx: Option>, - rts: Option>, - cts: Option>, - de: Option>, + _peri: Peri<'d, T>, + rx: Option>, + tx: Option>, + rts: Option>, + cts: Option>, + de: Option>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], config: Config, @@ -286,6 +417,11 @@ impl<'d> BufferedUart<'d> { let state = T::buffered_state(); let kernel_clock = T::frequency(); + state.half_duplex_readback.store( + config.duplex == Duplex::Half(HalfDuplexReadback::Readback), + Ordering::Relaxed, + ); + let mut this = Self { rx: BufferedUartRx { info, @@ -293,6 +429,7 @@ impl<'d> BufferedUart<'d> { kernel_clock, rx, rts, + is_borrowed: false, }, tx: BufferedUartTx { info, @@ -301,6 +438,7 @@ impl<'d> BufferedUart<'d> { tx, cts, de, + is_borrowed: false, }, }; this.enable_and_configure(tx_buffer, rx_buffer, &config)?; @@ -319,8 +457,10 @@ impl<'d> BufferedUart<'d> { info.rcc.enable_and_reset(); + assert!(!tx_buffer.is_empty()); let len = tx_buffer.len(); unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + assert!(!rx_buffer.is_empty()); let len = rx_buffer.len(); unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; @@ -329,12 +469,20 @@ impl<'d> BufferedUart<'d> { w.set_ctse(self.tx.cts.is_some()); #[cfg(not(any(usart_v1, usart_v2)))] w.set_dem(self.tx.de.is_some()); + w.set_hdsel(config.duplex.is_half()); }); configure(info, self.rx.kernel_clock, &config, true, true)?; info.regs.cr1().modify(|w| { w.set_rxneie(true); w.set_idleie(true); + + if config.duplex.is_half() { + // The te and re bits will be set by write, read and flush methods. + // Receiver should be enabled by default for Half-Duplex. + w.set_te(false); + w.set_re(true); + } }); info.interrupt.unpend(); @@ -348,6 +496,31 @@ impl<'d> BufferedUart<'d> { (self.tx, self.rx) } + /// Split the Uart into a transmitter and receiver, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (BufferedUartTx<'_>, BufferedUartRx<'_>) { + ( + BufferedUartTx { + info: self.tx.info, + state: self.tx.state, + kernel_clock: self.tx.kernel_clock, + tx: self.tx.tx.as_mut().map(Peri::reborrow), + cts: self.tx.cts.as_mut().map(Peri::reborrow), + de: self.tx.de.as_mut().map(Peri::reborrow), + is_borrowed: true, + }, + BufferedUartRx { + info: self.rx.info, + state: self.rx.state, + kernel_clock: self.rx.kernel_clock, + rx: self.rx.rx.as_mut().map(Peri::reborrow), + rts: self.rx.rts.as_mut().map(Peri::reborrow), + is_borrowed: true, + }, + ) + } + /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { reconfigure(self.rx.info, self.rx.kernel_clock, config)?; @@ -359,6 +532,18 @@ impl<'d> BufferedUart<'d> { Ok(()) } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break() + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + self.tx.set_baudrate(baudrate)?; + self.rx.set_baudrate(baudrate)?; + Ok(()) + } } impl<'d> BufferedUartRx<'d> { @@ -453,6 +638,11 @@ impl<'d> BufferedUartRx<'d> { Ok(()) } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } } impl<'d> BufferedUartTx<'d> { @@ -538,44 +728,58 @@ impl<'d> BufferedUartTx<'d> { Ok(()) } + + /// Send break character + pub fn send_break(&self) { + send_break(&self.info.regs); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } } impl<'d> Drop for BufferedUartRx<'d> { fn drop(&mut self) { - let state = self.state; - unsafe { - state.rx_buf.deinit(); + if !self.is_borrowed { + let state = self.state; + unsafe { + state.rx_buf.deinit(); - // TX is inactive if the the buffer is not available. - // We can now unregister the interrupt handler - if state.tx_buf.len() == 0 { - self.info.interrupt.disable(); + // TX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if state.tx_buf.len() == 0 { + self.info.interrupt.disable(); + } } - } - self.rx.as_ref().map(|x| x.set_as_disconnected()); - self.rts.as_ref().map(|x| x.set_as_disconnected()); - drop_tx_rx(self.info, state); + self.rx.as_ref().map(|x| x.set_as_disconnected()); + self.rts.as_ref().map(|x| x.set_as_disconnected()); + drop_tx_rx(self.info, state); + } } } impl<'d> Drop for BufferedUartTx<'d> { fn drop(&mut self) { - let state = self.state; - unsafe { - state.tx_buf.deinit(); + if !self.is_borrowed { + let state = self.state; + unsafe { + state.tx_buf.deinit(); - // RX is inactive if the the buffer is not available. - // We can now unregister the interrupt handler - if state.rx_buf.len() == 0 { - self.info.interrupt.disable(); + // RX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if state.rx_buf.len() == 0 { + self.info.interrupt.disable(); + } } - } - self.tx.as_ref().map(|x| x.set_as_disconnected()); - self.cts.as_ref().map(|x| x.set_as_disconnected()); - self.de.as_ref().map(|x| x.set_as_disconnected()); - drop_tx_rx(self.info, state); + self.tx.as_ref().map(|x| x.set_as_disconnected()); + self.cts.as_ref().map(|x| x.set_as_disconnected()); + self.de.as_ref().map(|x| x.set_as_disconnected()); + drop_tx_rx(self.info, state); + } } } @@ -704,26 +908,17 @@ impl<'d> embedded_hal_02::serial::Read for BufferedUartRx<'d> { type Error = Error; fn read(&mut self) -> Result> { - let r = self.info.regs; - unsafe { - let sr = sr(r).read(); - if sr.pe() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Parity)) - } else if sr.fe() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Framing)) - } else if sr.ne() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Noise)) - } else if sr.ore() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Overrun)) - } else if sr.rxne() { - Ok(rdr(r).read_volatile()) - } else { - Err(nb::Error::WouldBlock) + let state = self.state; + let mut rx_reader = unsafe { state.rx_buf.reader() }; + + let do_pend = state.rx_buf.is_full(); + if let Some(data) = rx_reader.pop_one() { + if do_pend { + self.info.interrupt.pend(); } + Ok(data) + } else { + Err(nb::Error::WouldBlock) } } } diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 7ed3793a1..b3f8bc00c 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -9,7 +9,7 @@ use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::drop::OnDrop; -use embassy_hal_internal::PeripheralRef; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use futures_util::future::{select, Either}; @@ -18,11 +18,6 @@ use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::interrupt::typelevel::Interrupt as _; use crate::interrupt::{self, Interrupt, InterruptExt}; use crate::mode::{Async, Blocking, Mode}; -#[allow(unused_imports)] -#[cfg(not(any(usart_v1, usart_v2)))] -use crate::pac::usart::regs::Isr as Sr; -#[cfg(any(usart_v1, usart_v2))] -use crate::pac::usart::regs::Sr; #[cfg(not(any(usart_v1, usart_v2)))] use crate::pac::usart::Lpuart as Regs; #[cfg(any(usart_v1, usart_v2))] @@ -30,7 +25,7 @@ use crate::pac::usart::Usart as Regs; use crate::pac::usart::{regs, vals}; use crate::rcc::{RccInfo, SealedRccPeripheral}; use crate::time::Hertz; -use crate::Peripheral; +use crate::Peri; /// Interrupt handler. pub struct InterruptHandler { @@ -69,6 +64,12 @@ unsafe fn on_interrupt(r: Regs, s: &'static State) { // disable idle line detection w.set_idleie(false); }); + } else if cr1.tcie() && sr.tc() { + // Transmission complete detected + r.cr1().modify(|w| { + // disable Transmission complete interrupt + w.set_tcie(false); + }); } else if cr1.rxneie() { // We cannot check the RXNE flag as it is auto-cleared by the DMA controller @@ -79,12 +80,15 @@ unsafe fn on_interrupt(r: Regs, s: &'static State) { compiler_fence(Ordering::SeqCst); s.rx_waker.wake(); + s.tx_waker.wake(); } #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Number of data bits pub enum DataBits { + /// 7 Data Bits + DataBits7, /// 8 Data Bits DataBits8, /// 9 Data Bits @@ -117,6 +121,57 @@ pub enum StopBits { STOP1P5, } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Enables or disables receiver so written data are read back in half-duplex mode +pub enum HalfDuplexReadback { + /// Disables receiver so written data are not read back + NoReadback, + /// Enables receiver so written data are read back + Readback, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Half duplex IO mode +pub enum OutputConfig { + /// Push pull allows for faster baudrates, no internal pullup + PushPull, + /// Open drain output (external pull up needed) + OpenDrain, + #[cfg(not(gpio_v1))] + /// Open drain output with internal pull up resistor + OpenDrainPullUp, +} + +impl OutputConfig { + const fn af_type(self) -> AfType { + match self { + OutputConfig::PushPull => AfType::output(OutputType::PushPull, Speed::Medium), + OutputConfig::OpenDrain => AfType::output(OutputType::OpenDrain, Speed::Medium), + #[cfg(not(gpio_v1))] + OutputConfig::OpenDrainPullUp => AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up), + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Duplex mode +pub enum Duplex { + /// Full duplex + Full, + /// Half duplex with possibility to read back written data + Half(HalfDuplexReadback), +} + +impl Duplex { + /// Returns true if half-duplex + fn is_half(&self) -> bool { + matches!(self, Duplex::Half(_)) + } +} + #[non_exhaustive] #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -128,6 +183,8 @@ pub enum ConfigError { BaudrateTooHigh, /// Rx or Tx not enabled RxOrTxNotEnabled, + /// Data bits and parity combination not supported + DataParityNotSupported, } #[non_exhaustive] @@ -167,25 +224,40 @@ pub struct Config { #[cfg(any(usart_v3, usart_v4))] pub invert_rx: bool, + /// Set the pull configuration for the RX pin. + pub rx_pull: Pull, + + /// Set the pull configuration for the CTS pin. + pub cts_pull: Pull, + + /// Set the pin configuration for the TX pin. + pub tx_config: OutputConfig, + + /// Set the pin configuration for the RTS pin. + pub rts_config: OutputConfig, + + /// Set the pin configuration for the DE pin. + pub de_config: OutputConfig, + // private: set by new_half_duplex, not by the user. - half_duplex: bool, + duplex: Duplex, } impl Config { fn tx_af(&self) -> AfType { #[cfg(any(usart_v3, usart_v4))] if self.swap_rx_tx { - return AfType::input(Pull::None); + return AfType::input(self.rx_pull); }; - AfType::output(OutputType::PushPull, Speed::Medium) + self.tx_config.af_type() } fn rx_af(&self) -> AfType { #[cfg(any(usart_v3, usart_v4))] if self.swap_rx_tx { - return AfType::output(OutputType::PushPull, Speed::Medium); + return self.tx_config.af_type(); }; - AfType::input(Pull::None) + AfType::input(self.rx_pull) } } @@ -206,7 +278,12 @@ impl Default for Config { invert_tx: false, #[cfg(any(usart_v3, usart_v4))] invert_rx: false, - half_duplex: false, + rx_pull: Pull::None, + cts_pull: Pull::None, + tx_config: OutputConfig::PushPull, + rts_config: OutputConfig::PushPull, + de_config: OutputConfig::PushPull, + duplex: Duplex::Full, } } } @@ -228,6 +305,22 @@ pub enum Error { BufferTooLong, } +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let message = match self { + Self::Framing => "Framing Error", + Self::Noise => "Noise Error", + Self::Overrun => "RX Buffer Overrun", + Self::Parity => "Parity Check Error", + Self::BufferTooLong => "Buffer too large for DMA", + }; + + write!(f, "{}", message) + } +} + +impl core::error::Error for Error {} + enum ReadCompletionEvent { // DMA Read transfer completed first DmaCompleted, @@ -266,10 +359,11 @@ pub struct UartTx<'d, M: Mode> { info: &'static Info, state: &'static State, kernel_clock: Hertz, - tx: Option>, - cts: Option>, - de: Option>, + tx: Option>, + cts: Option>, + de: Option>, tx_dma: Option>, + duplex: Duplex, _phantom: PhantomData, } @@ -315,12 +409,12 @@ pub struct UartRx<'d, M: Mode> { info: &'static Info, state: &'static State, kernel_clock: Hertz, - rx: Option>, - rts: Option>, + rx: Option>, + rts: Option>, rx_dma: Option>, detect_previous_overrun: bool, #[cfg(any(usart_v1, usart_v2))] - buffered_sr: stm32_metapac::usart::regs::Sr, + buffered_sr: regs::Sr, _phantom: PhantomData, } @@ -336,32 +430,26 @@ impl<'d, M: Mode> SetConfig for UartRx<'d, M> { impl<'d> UartTx<'d, Async> { /// Useful if you only want Uart Tx. It saves 1 pin and consumes a little less power. pub fn new( - peri: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + tx_dma: Peri<'d, impl TxDma>, config: Config, ) -> Result { - Self::new_inner( - peri, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), - None, - new_dma!(tx_dma), - config, - ) + Self::new_inner(peri, new_pin!(tx, config.tx_af()), None, new_dma!(tx_dma), config) } /// Create a new tx-only UART with a clear-to-send pin pub fn new_with_cts( - peri: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + cts: Peri<'d, impl CtsPin>, + tx_dma: Peri<'d, impl TxDma>, config: Config, ) -> Result { Self::new_inner( peri, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(cts, AfType::input(Pull::None)), + new_pin!(tx, config.tx_af()), + new_pin!(cts, AfType::input(config.cts_pull)), new_dma!(tx_dma), config, ) @@ -371,13 +459,7 @@ impl<'d> UartTx<'d, Async> { pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { let r = self.info.regs; - // Enable Transmitter and disable Receiver for Half-Duplex mode - let mut cr1 = r.cr1().read(); - if r.cr3().read().hdsel() && !cr1.te() { - cr1.set_te(true); - cr1.set_re(false); - r.cr1().write_value(cr1); - } + half_duplex_set_rx_tx_before_write(&r, self.duplex == Duplex::Half(HalfDuplexReadback::Readback)); let ch = self.tx_dma.as_mut().unwrap(); r.cr3().modify(|reg| { @@ -392,7 +474,7 @@ impl<'d> UartTx<'d, Async> { /// Wait until transmission complete pub async fn flush(&mut self) -> Result<(), Error> { - self.blocking_flush() + flush(&self.info, &self.state).await } } @@ -401,30 +483,24 @@ impl<'d> UartTx<'d, Blocking> { /// /// Useful if you only want Uart Tx. It saves 1 pin and consumes a little less power. pub fn new_blocking( - peri: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, config: Config, ) -> Result { - Self::new_inner( - peri, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), - None, - None, - config, - ) + Self::new_inner(peri, new_pin!(tx, config.tx_af()), None, None, config) } /// Create a new blocking tx-only UART with a clear-to-send pin pub fn new_blocking_with_cts( - peri: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + cts: Peri<'d, impl CtsPin>, config: Config, ) -> Result { Self::new_inner( peri, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(cts, AfType::input(Pull::None)), + new_pin!(tx, config.tx_af()), + new_pin!(cts, AfType::input(config.cts_pull)), None, config, ) @@ -433,9 +509,9 @@ impl<'d> UartTx<'d, Blocking> { impl<'d, M: Mode> UartTx<'d, M> { fn new_inner( - _peri: impl Peripheral

+ 'd, - tx: Option>, - cts: Option>, + _peri: Peri<'d, T>, + tx: Option>, + cts: Option>, tx_dma: Option>, config: Config, ) -> Result { @@ -447,6 +523,7 @@ impl<'d, M: Mode> UartTx<'d, M> { cts, de: None, tx_dma, + duplex: config.duplex, _phantom: PhantomData, }; this.enable_and_configure(&config)?; @@ -477,13 +554,7 @@ impl<'d, M: Mode> UartTx<'d, M> { pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { let r = self.info.regs; - // Enable Transmitter and disable Receiver for Half-Duplex mode - let mut cr1 = r.cr1().read(); - if r.cr3().read().hdsel() && !cr1.te() { - cr1.set_te(true); - cr1.set_re(false); - r.cr1().write_value(cr1); - } + half_duplex_set_rx_tx_before_write(&r, self.duplex == Duplex::Half(HalfDuplexReadback::Readback)); for &b in buffer { while !sr(r).read().txe() {} @@ -496,53 +567,110 @@ impl<'d, M: Mode> UartTx<'d, M> { pub fn blocking_flush(&mut self) -> Result<(), Error> { blocking_flush(self.info) } + + /// Send break character + pub fn send_break(&self) { + send_break(&self.info.regs); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } +} + +/// Wait until transmission complete +async fn flush(info: &Info, state: &State) -> Result<(), Error> { + let r = info.regs; + if r.cr1().read().te() && !sr(r).read().tc() { + r.cr1().modify(|w| { + // enable Transmission Complete interrupt + w.set_tcie(true); + }); + + compiler_fence(Ordering::SeqCst); + + // future which completes when Transmission complete is detected + let abort = poll_fn(move |cx| { + state.tx_waker.register(cx.waker()); + + let sr = sr(r).read(); + if sr.tc() { + // Transmission complete detected + return Poll::Ready(()); + } + + Poll::Pending + }); + + abort.await; + } + + Ok(()) } fn blocking_flush(info: &Info) -> Result<(), Error> { let r = info.regs; - while !sr(r).read().tc() {} - - // Enable Receiver after transmission complete for Half-Duplex mode - if r.cr3().read().hdsel() { - r.cr1().modify(|reg| reg.set_re(true)); + if r.cr1().read().te() { + while !sr(r).read().tc() {} } Ok(()) } +/// Send break character +pub fn send_break(regs: &Regs) { + // Busy wait until previous break has been sent + #[cfg(any(usart_v1, usart_v2))] + while regs.cr1().read().sbk() {} + #[cfg(any(usart_v3, usart_v4))] + while regs.isr().read().sbkf() {} + + // Send break right after completing the current character transmission + #[cfg(any(usart_v1, usart_v2))] + regs.cr1().modify(|w| w.set_sbk(true)); + #[cfg(any(usart_v3, usart_v4))] + regs.rqr().write(|w| w.set_sbkrq(true)); +} + +/// Enable Transmitter and disable Receiver for Half-Duplex mode +/// In case of readback, keep Receiver enabled +fn half_duplex_set_rx_tx_before_write(r: &Regs, enable_readback: bool) { + let mut cr1 = r.cr1().read(); + if r.cr3().read().hdsel() && !cr1.te() { + cr1.set_te(true); + cr1.set_re(enable_readback); + r.cr1().write_value(cr1); + } +} + impl<'d> UartRx<'d, Async> { /// Create a new rx-only UART with no hardware flow control. /// /// Useful if you only want Uart Rx. It saves 1 pin and consumes a little less power. pub fn new( - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - rx: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + rx: Peri<'d, impl RxPin>, + rx_dma: Peri<'d, impl RxDma>, config: Config, ) -> Result { - Self::new_inner( - peri, - new_pin!(rx, AfType::input(Pull::None)), - None, - new_dma!(rx_dma), - config, - ) + Self::new_inner(peri, new_pin!(rx, config.rx_af()), None, new_dma!(rx_dma), config) } /// Create a new rx-only UART with a request-to-send pin pub fn new_with_rts( - peri: impl Peripheral

+ 'd, + peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - rx: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + rx_dma: Peri<'d, impl RxDma>, config: Config, ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), - new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, config.rx_af()), + new_pin!(rts, config.rts_config.af_type()), new_dma!(rx_dma), config, ) @@ -570,12 +698,17 @@ impl<'d> UartRx<'d, Async> { // Call flush for Half-Duplex mode if some bytes were written and flush was not called. // It prevents reading of bytes which have just been written. if r.cr3().read().hdsel() && r.cr1().read().te() { - blocking_flush(self.info)?; + flush(&self.info, &self.state).await?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); } // make sure USART state is restored to neutral state when this future is dropped let on_drop = OnDrop::new(move || { - // defmt::trace!("Clear all USART interrupts and DMA Read Request"); // clear all interrupts and DMA Rx Request r.cr1().modify(|w| { // disable RXNE interrupt @@ -767,24 +900,24 @@ impl<'d> UartRx<'d, Blocking> { /// /// Useful if you only want Uart Rx. It saves 1 pin and consumes a little less power. pub fn new_blocking( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, config: Config, ) -> Result { - Self::new_inner(peri, new_pin!(rx, AfType::input(Pull::None)), None, None, config) + Self::new_inner(peri, new_pin!(rx, config.rx_af()), None, None, config) } /// Create a new rx-only UART with a request-to-send pin pub fn new_blocking_with_rts( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, config: Config, ) -> Result { Self::new_inner( peri, - new_pin!(rx, AfType::input(Pull::None)), - new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, config.rx_af()), + new_pin!(rts, config.rts_config.af_type()), None, config, ) @@ -793,9 +926,9 @@ impl<'d> UartRx<'d, Blocking> { impl<'d, M: Mode> UartRx<'d, M> { fn new_inner( - _peri: impl Peripheral

+ 'd, - rx: Option>, - rts: Option>, + _peri: Peri<'d, T>, + rx: Option>, + rts: Option>, rx_dma: Option>, config: Config, ) -> Result { @@ -809,7 +942,7 @@ impl<'d, M: Mode> UartRx<'d, M> { rx_dma, detect_previous_overrun: config.detect_previous_overrun, #[cfg(any(usart_v1, usart_v2))] - buffered_sr: stm32_metapac::usart::regs::Sr(0), + buffered_sr: regs::Sr(0), }; this.enable_and_configure(&config)?; Ok(this) @@ -909,6 +1042,12 @@ impl<'d, M: Mode> UartRx<'d, M> { // It prevents reading of bytes which have just been written. if r.cr3().read().hdsel() && r.cr1().read().te() { blocking_flush(self.info)?; + + // Disable Transmitter and enable Receiver after flush + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); } for b in buffer { @@ -917,6 +1056,11 @@ impl<'d, M: Mode> UartRx<'d, M> { } Ok(()) } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } } impl<'d, M: Mode> Drop for UartTx<'d, M> { @@ -952,12 +1096,12 @@ fn drop_tx_rx(info: &Info, state: &State) { impl<'d> Uart<'d, Async> { /// Create a new bidirectional UART pub fn new( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, config: Config, ) -> Result { Self::new_inner( @@ -975,22 +1119,22 @@ impl<'d> Uart<'d, Async> { /// Create a new bidirectional UART with request-to-send and clear-to-send pins pub fn new_with_rtscts( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - rts: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, config: Config, ) -> Result { Self::new_inner( peri, new_pin!(rx, config.rx_af()), new_pin!(tx, config.tx_af()), - new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(cts, AfType::input(Pull::None)), + new_pin!(rts, config.rts_config.af_type()), + new_pin!(cts, AfType::input(config.cts_pull)), None, new_dma!(tx_dma), new_dma!(rx_dma), @@ -1001,13 +1145,13 @@ impl<'d> Uart<'d, Async> { #[cfg(not(any(usart_v1, usart_v2)))] /// Create a new bidirectional UART with a driver-enable pin pub fn new_with_de( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - de: impl Peripheral

> + 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + de: Peri<'d, impl DePin>, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, config: Config, ) -> Result { Self::new_inner( @@ -1016,7 +1160,7 @@ impl<'d> Uart<'d, Async> { new_pin!(tx, config.tx_af()), None, None, - new_pin!(de, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(de, config.de_config.af_type()), new_dma!(tx_dma), new_dma!(rx_dma), config, @@ -1029,29 +1173,31 @@ impl<'d> Uart<'d, Async> { /// (when it is available for your chip). There is no functional difference between these methods, as both /// allow bidirectional communication. /// - /// The pin is always released when no data is transmitted. Thus, it acts as a standard - /// I/O in idle or in reception. + /// The TX pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. It means that the I/O must be configured so that TX is + /// configured as alternate function open-drain with an external pull-up /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict /// on the line must be managed by software (for instance by using a centralized arbiter). #[doc(alias("HDSEL"))] pub fn new_half_duplex( - peri: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, mut config: Config, + readback: HalfDuplexReadback, ) -> Result { #[cfg(not(any(usart_v1, usart_v2)))] { config.swap_rx_tx = false; } - config.half_duplex = true; + config.duplex = Duplex::Half(readback); Self::new_inner( peri, None, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(tx, config.tx_af()), None, None, None, @@ -1073,21 +1219,22 @@ impl<'d> Uart<'d, Async> { #[cfg(not(any(usart_v1, usart_v2)))] #[doc(alias("HDSEL"))] pub fn new_half_duplex_on_rx( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, _irq: impl interrupt::typelevel::Binding> + 'd, - tx_dma: impl Peripheral

> + 'd, - rx_dma: impl Peripheral

> + 'd, + tx_dma: Peri<'d, impl TxDma>, + rx_dma: Peri<'d, impl RxDma>, mut config: Config, + readback: HalfDuplexReadback, ) -> Result { config.swap_rx_tx = true; - config.half_duplex = true; + config.duplex = Duplex::Half(readback); Self::new_inner( peri, None, None, - new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, config.rx_af()), None, None, new_dma!(tx_dma), @@ -1101,6 +1248,11 @@ impl<'d> Uart<'d, Async> { self.tx.write(buffer).await } + /// Wait until transmission complete + pub async fn flush(&mut self) -> Result<(), Error> { + self.tx.flush().await + } + /// Perform an asynchronous read into `buffer` pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await @@ -1115,9 +1267,9 @@ impl<'d> Uart<'d, Async> { impl<'d> Uart<'d, Blocking> { /// Create a new blocking bidirectional UART. pub fn new_blocking( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, config: Config, ) -> Result { Self::new_inner( @@ -1135,19 +1287,19 @@ impl<'d> Uart<'d, Blocking> { /// Create a new bidirectional UART with request-to-send and clear-to-send pins pub fn new_blocking_with_rtscts( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, config: Config, ) -> Result { Self::new_inner( peri, new_pin!(rx, config.rx_af()), new_pin!(tx, config.tx_af()), - new_pin!(rts, AfType::output(OutputType::PushPull, Speed::Medium)), - new_pin!(cts, AfType::input(Pull::None)), + new_pin!(rts, config.rts_config.af_type()), + new_pin!(cts, AfType::input(config.cts_pull)), None, None, None, @@ -1158,10 +1310,10 @@ impl<'d> Uart<'d, Blocking> { #[cfg(not(any(usart_v1, usart_v2)))] /// Create a new bidirectional UART with a driver-enable pin pub fn new_blocking_with_de( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, - tx: impl Peripheral

> + 'd, - de: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + tx: Peri<'d, impl TxPin>, + de: Peri<'d, impl DePin>, config: Config, ) -> Result { Self::new_inner( @@ -1170,7 +1322,7 @@ impl<'d> Uart<'d, Blocking> { new_pin!(tx, config.tx_af()), None, None, - new_pin!(de, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(de, config.de_config.af_type()), None, None, config, @@ -1189,20 +1341,21 @@ impl<'d> Uart<'d, Blocking> { /// on the line must be managed by software (for instance by using a centralized arbiter). #[doc(alias("HDSEL"))] pub fn new_blocking_half_duplex( - peri: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, mut config: Config, + readback: HalfDuplexReadback, ) -> Result { #[cfg(not(any(usart_v1, usart_v2)))] { config.swap_rx_tx = false; } - config.half_duplex = true; + config.duplex = Duplex::Half(readback); Self::new_inner( peri, None, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(tx, config.tx_af()), None, None, None, @@ -1224,18 +1377,19 @@ impl<'d> Uart<'d, Blocking> { #[cfg(not(any(usart_v1, usart_v2)))] #[doc(alias("HDSEL"))] pub fn new_blocking_half_duplex_on_rx( - peri: impl Peripheral

+ 'd, - rx: impl Peripheral

> + 'd, + peri: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, mut config: Config, + readback: HalfDuplexReadback, ) -> Result { config.swap_rx_tx = true; - config.half_duplex = true; + config.duplex = Duplex::Half(readback); Self::new_inner( peri, None, None, - new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, config.rx_af()), None, None, None, @@ -1247,12 +1401,12 @@ impl<'d> Uart<'d, Blocking> { impl<'d, M: Mode> Uart<'d, M> { fn new_inner( - _peri: impl Peripheral

+ 'd, - rx: Option>, - tx: Option>, - rts: Option>, - cts: Option>, - de: Option>, + _peri: Peri<'d, T>, + rx: Option>, + tx: Option>, + rts: Option>, + cts: Option>, + de: Option>, tx_dma: Option>, rx_dma: Option>, config: Config, @@ -1271,6 +1425,7 @@ impl<'d, M: Mode> Uart<'d, M> { cts, de, tx_dma, + duplex: config.duplex, }, rx: UartRx { _phantom: PhantomData, @@ -1282,7 +1437,7 @@ impl<'d, M: Mode> Uart<'d, M> { rx_dma, detect_previous_overrun: config.detect_previous_overrun, #[cfg(any(usart_v1, usart_v2))] - buffered_sr: stm32_metapac::usart::regs::Sr(0), + buffered_sr: regs::Sr(0), }, }; this.enable_and_configure(&config)?; @@ -1336,6 +1491,25 @@ impl<'d, M: Mode> Uart<'d, M> { pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { (self.tx, self.rx) } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut UartTx<'d, M>, &mut UartRx<'d, M>) { + (&mut self.tx, &mut self.rx) + } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break(); + } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + self.tx.set_baudrate(baudrate)?; + self.rx.set_baudrate(baudrate)?; + Ok(()) + } } fn reconfigure(info: &Info, kernel_clock: Hertz, config: &Config) -> Result<(), ConfigError> { @@ -1351,20 +1525,33 @@ fn reconfigure(info: &Info, kernel_clock: Hertz, config: &Config) -> Result<(), Ok(()) } -fn configure( - info: &Info, - kernel_clock: Hertz, - config: &Config, - enable_rx: bool, - enable_tx: bool, -) -> Result<(), ConfigError> { - let r = info.regs; - let kind = info.kind; +fn calculate_brr(baud: u32, pclk: u32, presc: u32, mul: u32) -> u32 { + // The calculation to be done to get the BRR is `mul * pclk / presc / baud` + // To do this in 32-bit only we can't multiply `mul` and `pclk` + let clock = pclk / presc; - if !enable_rx && !enable_tx { - return Err(ConfigError::RxOrTxNotEnabled); - } + // The mul is applied as the last operation to prevent overflow + let brr = clock / baud * mul; + // The BRR calculation will be a bit off because of integer rounding. + // Because we multiplied our inaccuracy with mul, our rounding now needs to be in proportion to mul. + let rounding = ((clock % baud) * mul + (baud / 2)) / baud; + + brr + rounding +} + +fn set_baudrate(info: &Info, kernel_clock: Hertz, baudrate: u32) -> Result<(), ConfigError> { + info.interrupt.disable(); + + set_usart_baudrate(info, kernel_clock, baudrate)?; + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + + Ok(()) +} + +fn find_and_set_brr(r: Regs, kind: Kind, kernel_clock: Hertz, baudrate: u32) -> Result { #[cfg(not(usart_v4))] static DIVS: [(u16, ()); 1] = [(1, ())]; @@ -1396,31 +1583,14 @@ fn configure( } }; - fn calculate_brr(baud: u32, pclk: u32, presc: u32, mul: u32) -> u32 { - // The calculation to be done to get the BRR is `mul * pclk / presc / baud` - // To do this in 32-bit only we can't multiply `mul` and `pclk` - let clock = pclk / presc; - - // The mul is applied as the last operation to prevent overflow - let brr = clock / baud * mul; - - // The BRR calculation will be a bit off because of integer rounding. - // Because we multiplied our inaccuracy with mul, our rounding now needs to be in proportion to mul. - let rounding = ((clock % baud) * mul + (baud / 2)) / baud; - - brr + rounding - } - - // UART must be disabled during configuration. - r.cr1().modify(|w| { - w.set_ue(false); - }); - + let mut found_brr = None; #[cfg(not(usart_v1))] let mut over8 = false; - let mut found_brr = None; + #[cfg(usart_v1)] + let over8 = false; + for &(presc, _presc_val) in &DIVS { - let brr = calculate_brr(config.baudrate, kernel_clock.0, presc as u32, mul); + let brr = calculate_brr(baudrate, kernel_clock.0, presc as u32, mul); trace!( "USART: presc={}, div=0x{:08x} (mantissa = {}, fraction = {})", presc, @@ -1451,18 +1621,70 @@ fn configure( } } - let brr = found_brr.ok_or(ConfigError::BaudrateTooLow)?; + match found_brr { + Some(brr) => { + #[cfg(not(usart_v1))] + let oversampling = if over8 { "8 bit" } else { "16 bit" }; + #[cfg(usart_v1)] + let oversampling = "default"; + trace!( + "Using {} oversampling, desired baudrate: {}, actual baudrate: {}", + oversampling, + baudrate, + kernel_clock.0 / brr * mul + ); + Ok(over8) + } + None => Err(ConfigError::BaudrateTooLow), + } +} + +fn set_usart_baudrate(info: &Info, kernel_clock: Hertz, baudrate: u32) -> Result<(), ConfigError> { + let r = info.regs; + r.cr1().modify(|w| { + // disable uart + w.set_ue(false); + }); #[cfg(not(usart_v1))] - let oversampling = if over8 { "8 bit" } else { "16 bit" }; + let over8 = find_and_set_brr(r, info.kind, kernel_clock, baudrate)?; #[cfg(usart_v1)] - let oversampling = "default"; - trace!( - "Using {} oversampling, desired baudrate: {}, actual baudrate: {}", - oversampling, - config.baudrate, - kernel_clock.0 / brr * mul - ); + let _over8 = find_and_set_brr(r, info.kind, kernel_clock, baudrate)?; + + r.cr1().modify(|w| { + // enable uart + w.set_ue(true); + + #[cfg(not(usart_v1))] + w.set_over8(vals::Over8::from_bits(over8 as _)); + }); + + Ok(()) +} + +fn configure( + info: &Info, + kernel_clock: Hertz, + config: &Config, + enable_rx: bool, + enable_tx: bool, +) -> Result<(), ConfigError> { + let r = info.regs; + let kind = info.kind; + + if !enable_rx && !enable_tx { + return Err(ConfigError::RxOrTxNotEnabled); + } + + // UART must be disabled during configuration. + r.cr1().modify(|w| { + w.set_ue(false); + }); + + #[cfg(not(usart_v1))] + let over8 = find_and_set_brr(r, kind, kernel_clock, config.baudrate)?; + #[cfg(usart_v1)] + let _over8 = find_and_set_brr(r, kind, kernel_clock, config.baudrate)?; r.cr2().write(|w| { w.set_stop(match config.stop_bits { @@ -1483,14 +1705,14 @@ fn configure( r.cr3().modify(|w| { #[cfg(not(usart_v1))] w.set_onebit(config.assume_noise_free); - w.set_hdsel(config.half_duplex); + w.set_hdsel(config.duplex.is_half()); }); r.cr1().write(|w| { // enable uart w.set_ue(true); - if config.half_duplex { + if config.duplex.is_half() { // The te and re bits will be set by write, read and flush methods. // Receiver should be enabled by default for Half-Duplex. w.set_te(false); @@ -1502,31 +1724,66 @@ fn configure( w.set_re(enable_rx); } - // configure word size - // if using odd or even parity it must be configured to 9bits - w.set_m0(if config.parity != Parity::ParityNone { - trace!("USART: m0: vals::M0::BIT9"); - vals::M0::BIT9 - } else { - trace!("USART: m0: vals::M0::BIT8"); - vals::M0::BIT8 - }); - // configure parity - w.set_pce(config.parity != Parity::ParityNone); - w.set_ps(match config.parity { - Parity::ParityOdd => { - trace!("USART: set_ps: vals::Ps::ODD"); - vals::Ps::ODD + // configure word size and parity, since the parity bit is inserted into the MSB position, + // it increases the effective word size + match (config.parity, config.data_bits) { + (Parity::ParityNone, DataBits::DataBits8) => { + trace!("USART: m0: 8 data bits, no parity"); + w.set_m0(vals::M0::BIT8); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(false); } - Parity::ParityEven => { - trace!("USART: set_ps: vals::Ps::EVEN"); - vals::Ps::EVEN + (Parity::ParityNone, DataBits::DataBits9) => { + trace!("USART: m0: 9 data bits, no parity"); + w.set_m0(vals::M0::BIT9); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(false); + } + #[cfg(any(usart_v3, usart_v4))] + (Parity::ParityNone, DataBits::DataBits7) => { + trace!("USART: m0: 7 data bits, no parity"); + w.set_m0(vals::M0::BIT8); + w.set_m1(vals::M1::BIT7); + w.set_pce(false); + } + (Parity::ParityEven, DataBits::DataBits8) => { + trace!("USART: m0: 8 data bits, even parity"); + w.set_m0(vals::M0::BIT9); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::EVEN); + } + (Parity::ParityEven, DataBits::DataBits7) => { + trace!("USART: m0: 7 data bits, even parity"); + w.set_m0(vals::M0::BIT8); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::EVEN); + } + (Parity::ParityOdd, DataBits::DataBits8) => { + trace!("USART: m0: 8 data bits, odd parity"); + w.set_m0(vals::M0::BIT9); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::ODD); + } + (Parity::ParityOdd, DataBits::DataBits7) => { + trace!("USART: m0: 7 data bits, odd parity"); + w.set_m0(vals::M0::BIT8); + #[cfg(any(usart_v3, usart_v4))] + w.set_m1(vals::M1::M0); + w.set_pce(true); + w.set_ps(vals::Ps::ODD); } _ => { - trace!("USART: set_ps: vals::Ps::EVEN"); - vals::Ps::EVEN + return Err(ConfigError::DataParityNotSupported); } - }); + } #[cfg(not(usart_v1))] w.set_over8(vals::Over8::from_bits(over8 as _)); #[cfg(usart_v4)] @@ -1534,7 +1791,9 @@ fn configure( trace!("USART: set_fifoen: true (usart_v4)"); w.set_fifoen(true); } - }); + + Ok(()) + })?; Ok(()) } @@ -1672,7 +1931,7 @@ impl embedded_io_async::Write for Uart<'_, Async> { } async fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() + self.flush().await } } @@ -1683,7 +1942,7 @@ impl embedded_io_async::Write for UartTx<'_, Async> { } async fn flush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() + self.flush().await } } @@ -1749,6 +2008,7 @@ enum Kind { struct State { rx_waker: AtomicWaker, + tx_waker: AtomicWaker, tx_rx_refcount: AtomicU8, } @@ -1756,6 +2016,7 @@ impl State { const fn new() -> Self { Self { rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), tx_rx_refcount: AtomicU8::new(0), } } @@ -1777,7 +2038,7 @@ pub(crate) trait SealedInstance: crate::rcc::RccPeripheral { /// USART peripheral instance trait. #[allow(private_bounds)] -pub trait Instance: Peripheral

+ SealedInstance + 'static + Send { +pub trait Instance: SealedInstance + PeripheralType + 'static + Send { /// Interrupt for this peripheral. type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 8cf75933a..1d4a44896 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -4,25 +4,85 @@ use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; -use embassy_hal_internal::PeripheralRef; +use embedded_io_async::ReadReady; use futures_util::future::{select, Either}; -use super::{clear_interrupt_flags, rdr, reconfigure, sr, Config, ConfigError, Error, Info, State, UartRx}; +use super::{rdr, reconfigure, set_baudrate, sr, Config, ConfigError, Error, Info, State, UartRx}; use crate::dma::ReadableRingBuffer; use crate::gpio::{AnyPin, SealedPin as _}; use crate::mode::Async; use crate::time::Hertz; -use crate::usart::{Regs, Sr}; +use crate::usart::Regs; +use crate::Peri; /// Rx-only Ring-buffered UART Driver /// /// Created with [UartRx::into_ring_buffered] +/// +/// ### Notes on 'waiting for bytes' +/// +/// The `read(buf)` (but not `read()`) and `read_exact(buf)` functions +/// may need to wait for bytes to arrive, if the ring buffer does not +/// contain enough bytes to fill the buffer passed by the caller of +/// the function, or is empty. +/// +/// Waiting for bytes operates in one of two modes, depending on +/// the behavior of the sender and the size of the buffer passed +/// to the function: +/// +/// - If the sender sends intermittently, the 'idle line' +/// condition will be detected when the sender stops, and any +/// bytes in the ring buffer will be returned. If there are no +/// bytes in the buffer, the check will be repeated each time the +/// 'idle line' condition is detected, so if the sender sends just +/// a single byte, it will be returned once the 'idle line' +/// condition is detected. +/// +/// - If the sender sends continuously, the call will wait until +/// the DMA controller indicates that it has written to either the +/// middle byte or last byte of the ring buffer ('half transfer' +/// or 'transfer complete', respectively). This does not indicate +/// the buffer is half-full or full, though, because the DMA +/// controller does not detect those conditions; it sends an +/// interrupt when those specific buffer addresses have been +/// written. +/// +/// In both cases this will result in variable latency due to the +/// buffering effect. For example, if the baudrate is 2400 bps, and +/// the configuration is 8 data bits, no parity bit, and one stop bit, +/// then a byte will be received every ~4.16ms. If the ring buffer is +/// 32 bytes, then a 'wait for bytes' delay may have to wait for 16 +/// bytes in the worst case, resulting in a delay (latency) of +/// ~62.46ms for the first byte in the ring buffer. If the sender +/// sends only 6 bytes and then stops, but the buffer was empty when +/// the read function was called, then those bytes may not be returned +/// until ~24.96ms after the first byte was received (time for 5 +/// additional bytes plus the 'idle frame' which triggers the 'idle +/// line' condition). +/// +/// Applications subject to this latency must be careful if they +/// also apply timeouts during reception, as it may appear (to +/// them) that the sender has stopped sending when it did not. In +/// the example above, a 50ms timeout (12 bytes at 2400bps) might +/// seem to be reasonable to detect that the sender has stopped +/// sending, but would be falsely triggered in the worst-case +/// buffer delay scenario. +/// +/// Note: This latency is caused by the limited capabilities of the +/// STM32 DMA controller; since it cannot generate an interrupt when +/// it stores a byte into an empty ring buffer, or in any other +/// configurable conditions, it is not possible to take notice of the +/// contents of the ring buffer more quickly without introducing +/// polling. As a result the latency can be reduced by calling the +/// read functions repeatedly with smaller buffers to receive the +/// available bytes, as each call to a read function will explicitly +/// check the ring buffer for available bytes. pub struct RingBufferedUartRx<'d> { info: &'static Info, state: &'static State, kernel_clock: Hertz, - rx: Option>, - rts: Option>, + rx: Option>, + rts: Option>, ring_buf: ReadableRingBuffer<'d, u8>, } @@ -71,33 +131,18 @@ impl<'d> UartRx<'d, Async> { } impl<'d> RingBufferedUartRx<'d> { - /// 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 - self.ring_buf.clear(); - - self.setup_uart(); - - Ok(()) - } - - fn stop(&mut self, err: Error) -> Result { - self.teardown_uart(); - - Err(err) - } - /// Reconfigure the driver pub fn set_config(&mut self, config: &Config) -> Result<(), ConfigError> { reconfigure(self.info, self.kernel_clock, config) } - /// Start uart background receive - fn setup_uart(&mut self) { - // fence before starting DMA. + /// Configure and start the DMA backed UART receiver + /// + /// Note: This is also done automatically by the read functions if + /// required. + pub fn start_uart(&mut self) { + // Clear the buffer so that it is ready to receive data compiler_fence(Ordering::SeqCst); - - // start the dma controller self.ring_buf.start(); let r = self.info.regs; @@ -118,9 +163,9 @@ impl<'d> RingBufferedUartRx<'d> { }); } - /// Stop uart background receive - fn teardown_uart(&mut self) { - self.ring_buf.request_stop(); + /// Stop DMA backed UART receiver + fn stop_uart(&mut self) { + self.ring_buf.request_pause(); let r = self.info.regs; // clear all interrupts and DMA Rx Request @@ -142,23 +187,40 @@ impl<'d> RingBufferedUartRx<'d> { compiler_fence(Ordering::SeqCst); } - /// Read bytes that are readily available in the ring buffer. - /// If no bytes are currently available in the buffer the call waits until the some - /// bytes are available (at least one byte and at most half the buffer size) - /// - /// Background receive is started if `start()` has not been previously called. - /// - /// Receive in the background is terminated if an error is returned. - /// It must then manually be started again by calling `start()` or by re-calling `read()`. - pub async fn read(&mut self, buf: &mut [u8]) -> Result { + /// (Re-)start DMA and Uart if it is not running (has not been started yet or has failed), and + /// check for errors in status register. Error flags are checked/cleared first. + fn start_dma_or_check_errors(&mut self) -> Result<(), Error> { let r = self.info.regs; - // Start background receive if it was not already started + check_idle_and_errors(r)?; if !r.cr3().read().dmar() { - self.start()?; + self.start_uart(); } + Ok(()) + } - check_for_errors(clear_idle_flag(r))?; + /// Read bytes that are available in the ring buffer, or wait for + /// bytes to become available and return them. + /// + /// Background reception is started if necessary (if `start_uart()` had + /// not previously been called, or if an error was detected which + /// caused background reception to be stopped). + /// + /// Background reception is terminated when an error is returned. + /// It must be started again by calling `start_uart()` or by + /// calling a read function again. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.start_dma_or_check_errors()?; + + // In half-duplex mode, we need to disable the Transmitter and enable the Receiver + // since they can't operate simultaneously on the shared line + let r = self.info.regs; + if r.cr3().read().hdsel() && r.cr1().read().te() { + r.cr1().modify(|reg| { + reg.set_re(true); + reg.set_te(false); + }); + } loop { match self.ring_buf.read(buf) { @@ -167,14 +229,16 @@ impl<'d> RingBufferedUartRx<'d> { return Ok(len); } Err(_) => { - return self.stop(Error::Overrun); + self.stop_uart(); + return Err(Error::Overrun); } } match self.wait_for_data_or_idle().await { Ok(_) => {} Err(err) => { - return self.stop(err); + self.stop_uart(); + return Err(err); } } } @@ -184,8 +248,24 @@ impl<'d> RingBufferedUartRx<'d> { async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> { compiler_fence(Ordering::SeqCst); + // Future which completes when idle line is detected + let s = self.state; + let uart = poll_fn(|cx| { + s.rx_waker.register(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + if check_idle_and_errors(self.info.regs)? { + // Idle line is detected + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }); + let mut dma_init = false; - // Future which completes when there is dma is half full or full + // Future which completes when the DMA controller indicates it + // has written to the ring buffer's middle byte, or last byte let dma = poll_fn(|cx| { self.ring_buf.set_waker(cx.waker()); @@ -198,73 +278,70 @@ impl<'d> RingBufferedUartRx<'d> { status }); - // Future which completes when idle line is detected - let s = self.state; - let uart = poll_fn(|cx| { - s.rx_waker.register(cx.waker()); - - compiler_fence(Ordering::SeqCst); - - // Critical section is needed so that IDLE isn't set after - // our read but before we clear it. - let sr = critical_section::with(|_| clear_idle_flag(self.info.regs)); - - check_for_errors(sr)?; - - if sr.idle() { - // Idle line is detected - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - }); - - match select(dma, uart).await { - Either::Left(((), _)) => Ok(()), - Either::Right((result, _)) => result, + match select(uart, dma).await { + Either::Left((result, _)) => result, + Either::Right(((), _)) => Ok(()), } } + + /// Set baudrate + pub fn set_baudrate(&self, baudrate: u32) -> Result<(), ConfigError> { + set_baudrate(self.info, self.kernel_clock, baudrate) + } } impl Drop for RingBufferedUartRx<'_> { fn drop(&mut self) { - self.teardown_uart(); + self.stop_uart(); self.rx.as_ref().map(|x| x.set_as_disconnected()); self.rts.as_ref().map(|x| x.set_as_disconnected()); super::drop_tx_rx(self.info, self.state); } } -/// Return an error result if the Sr register has errors -fn check_for_errors(s: Sr) -> Result<(), Error> { - if s.pe() { +/// Check and clear idle and error interrupts, return true if idle, Err(e) on error +/// +/// All flags are read and cleared in a single step, respectively. When more than one flag is set +/// at the same time, all flags will be cleared but only one flag will be reported. So the other +/// flag(s) will gone missing unnoticed. The error flags are checked first, the idle flag last. +/// +/// For usart_v1 and usart_v2, all status flags must be handled together anyway because all flags +/// are cleared by a single read to the RDR register. +fn check_idle_and_errors(r: Regs) -> Result { + // Critical section is required so that the flags aren't set after read and before clear + let sr = critical_section::with(|_| { + // SAFETY: read only and we only use Rx related flags + let sr = sr(r).read(); + + #[cfg(any(usart_v3, usart_v4))] + r.icr().write(|w| { + w.set_idle(true); + w.set_pe(true); + w.set_fe(true); + w.set_ne(true); + w.set_ore(true); + }); + #[cfg(not(any(usart_v3, usart_v4)))] + unsafe { + // This read also clears the error and idle interrupt flags on v1 (TODO and v2?) + rdr(r).read_volatile() + }; + sr + }); + if sr.pe() { Err(Error::Parity) - } else if s.fe() { + } else if sr.fe() { Err(Error::Framing) - } else if s.ne() { + } else if sr.ne() { Err(Error::Noise) - } else if s.ore() { + } else if sr.ore() { Err(Error::Overrun) } else { - Ok(()) + r.cr1().modify(|w| w.set_idleie(true)); + Ok(sr.idle()) } } -/// Clear IDLE and return the Sr register -fn clear_idle_flag(r: Regs) -> Sr { - // SAFETY: read only and we only use Rx related flags - - let sr = sr(r).read(); - - // This read also clears the error and idle interrupt flags on v1. - unsafe { rdr(r).read_volatile() }; - clear_interrupt_flags(r, sr); - - r.cr1().modify(|w| w.set_idleie(true)); - - sr -} - impl embedded_io_async::ErrorType for RingBufferedUartRx<'_> { type Error = Error; } @@ -274,3 +351,43 @@ impl embedded_io_async::Read for RingBufferedUartRx<'_> { self.read(buf).await } } + +impl embedded_hal_nb::serial::Read for RingBufferedUartRx<'_> { + fn read(&mut self) -> nb::Result { + self.start_dma_or_check_errors()?; + + let mut buf = [0u8; 1]; + match self.ring_buf.read(&mut buf) { + Ok((0, _)) => Err(nb::Error::WouldBlock), + Ok((len, _)) => { + assert!(len == 1); + Ok(buf[0]) + } + Err(_) => { + self.stop_uart(); + Err(nb::Error::Other(Error::Overrun)) + } + } + } +} + +impl embedded_hal_nb::serial::ErrorType for RingBufferedUartRx<'_> { + type Error = Error; +} + +impl ReadReady for RingBufferedUartRx<'_> { + fn read_ready(&mut self) -> Result { + let len = self.ring_buf.len().map_err(|e| match e { + crate::dma::ringbuffer::Error::Overrun => Self::Error::Overrun, + crate::dma::ringbuffer::Error::DmaUnsynced => { + error!( + "Ringbuffer error: DmaUNsynced, driver implementation is + probably bugged please open an issue" + ); + // we report this as overrun since its recoverable in the same way + Self::Error::Overrun + } + })?; + Ok(len > 0) + } +} diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index ce9fe0a9b..ae5963420 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -13,9 +13,19 @@ fn common_init() { // Check the USB clock is enabled and running at exactly 48 MHz. // frequency() will panic if not enabled let freq = T::frequency(); + + // On the H7RS, the USBPHYC embeds a PLL accepting one of the input frequencies listed below and providing 48MHz to OTG_FS and 60MHz to OTG_HS internally + #[cfg(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs)))] + if ![16_000_000, 19_200_000, 20_000_000, 24_000_000, 26_000_000, 32_000_000].contains(&freq.0) { + panic!( + "USB clock should be one of 16, 19.2, 20, 24, 26, 32Mhz but is {} Hz. Please double-check your RCC settings.", + freq.0 + ) + } // Check frequency is within the 0.25% tolerance allowed by the spec. // Clock might not be exact 48Mhz due to rounding errors in PLL calculation, or if the user // has tight clock restrictions due to something else (like audio). + #[cfg(not(any(stm32h7rs, all(stm32u5, peri_usb_otg_hs))))] if freq.0.abs_diff(48_000_000) > 120_000 { panic!( "USB clock should be 48Mhz but is {} Hz. Please double-check your RCC settings.", @@ -48,6 +58,26 @@ fn common_init() { while !crate::pac::PWR.cr3().read().usb33rdy() {} } + #[cfg(stm32h7rs)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.csr2().modify(|w| { + w.set_usbregen(internal_regulator); + w.set_usb33den(true); + w.set_usbhsregen(true); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.csr2().read().usb33rdy() {} + } + #[cfg(stm32u5)] { // Enable USB power @@ -60,6 +90,16 @@ fn common_init() { // Wait for USB power to stabilize while !crate::pac::PWR.svmsr().read().vddusbrdy() {} + + // Now set up transceiver power if it's a OTG-HS + #[cfg(peri_usb_otg_hs)] + { + crate::pac::PWR.vosr().modify(|w| { + w.set_usbpwren(true); + w.set_usbboosten(true); + }); + while !crate::pac::PWR.vosr().read().usbboostrdy() {} + } } T::Interrupt::unpend(); diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index 8ee8dcc36..590d1a427 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -1,6 +1,6 @@ use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, Peripheral}; +use embassy_hal_internal::PeripheralType; use embassy_usb_driver::{EndpointAddress, EndpointAllocError, EndpointType, Event, Unsupported}; use embassy_usb_synopsys_otg::otg_v1::vals::Dspd; use embassy_usb_synopsys_otg::otg_v1::Otg; @@ -11,9 +11,9 @@ use embassy_usb_synopsys_otg::{ }; use crate::gpio::{AfType, OutputType, Speed}; -use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::rcc::{self, RccPeripheral}; +use crate::{interrupt, Peri}; const MAX_EP_COUNT: usize = 9; @@ -24,20 +24,15 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - trace!("irq"); let r = T::regs(); let state = T::state(); - - let setup_late_cnak = quirk_setup_late_cnak(r); - - on_interrupt_impl(r, state, T::ENDPOINT_COUNT, setup_late_cnak); + on_interrupt_impl(r, state, T::ENDPOINT_COUNT); } } macro_rules! config_ulpi_pins { ($($pin:ident),*) => { - into_ref!($($pin),*); - critical_section::with(|_| { + critical_section::with(|_| { $( $pin.set_as_af($pin.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); )* @@ -66,15 +61,13 @@ impl<'d, T: Instance> Driver<'d, T> { /// Must be large enough to fit all OUT endpoint max packet sizes. /// Endpoint allocation will fail if it is too small. pub fn new_fs( - _peri: impl Peripheral

+ 'd, + _peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - dp: impl Peripheral

> + 'd, - dm: impl Peripheral

> + 'd, + dp: Peri<'d, impl DpPin>, + dm: Peri<'d, impl DmPin>, ep_out_buffer: &'d mut [u8], config: Config, ) -> Self { - into_ref!(dp, dm); - dp.set_as_af(dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); dm.set_as_af(dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); @@ -87,7 +80,90 @@ impl<'d, T: Instance> Driver<'d, T> { extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, endpoint_count: T::ENDPOINT_COUNT, phy_type: PhyType::InternalFullSpeed, - quirk_setup_late_cnak: quirk_setup_late_cnak(regs), + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + + /// Initializes USB OTG peripheral with internal High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs( + _peri: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + _dp: Peri<'d, impl DpPin>, + _dm: Peri<'d, impl DmPin>, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + // For STM32U5 High speed pins need to be left in analog mode + #[cfg(not(all(stm32u5, peri_usb_otg_hs)))] + { + _dp.set_as_af(_dp.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + _dm.set_as_af(_dm.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + } + + let instance = OtgInstance { + regs: T::regs(), + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::InternalHighSpeed, + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + + /// Initializes USB OTG peripheral with external Full-speed PHY (usually, a High-speed PHY in Full-speed mode). + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_fs_ulpi( + _peri: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + ulpi_clk: Peri<'d, impl UlpiClkPin>, + ulpi_dir: Peri<'d, impl UlpiDirPin>, + ulpi_nxt: Peri<'d, impl UlpiNxtPin>, + ulpi_stp: Peri<'d, impl UlpiStpPin>, + ulpi_d0: Peri<'d, impl UlpiD0Pin>, + ulpi_d1: Peri<'d, impl UlpiD1Pin>, + ulpi_d2: Peri<'d, impl UlpiD2Pin>, + ulpi_d3: Peri<'d, impl UlpiD3Pin>, + ulpi_d4: Peri<'d, impl UlpiD4Pin>, + ulpi_d5: Peri<'d, impl UlpiD5Pin>, + ulpi_d6: Peri<'d, impl UlpiD6Pin>, + ulpi_d7: Peri<'d, impl UlpiD7Pin>, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + config_ulpi_pins!( + ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, + ulpi_d7 + ); + + let instance = OtgInstance { + regs: T::regs(), + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::ExternalFullSpeed, calculate_trdt_fn: calculate_trdt::, }; @@ -105,20 +181,20 @@ impl<'d, T: Instance> Driver<'d, T> { /// Must be large enough to fit all OUT endpoint max packet sizes. /// Endpoint allocation will fail if it is too small. pub fn new_hs_ulpi( - _peri: impl Peripheral

+ 'd, + _peri: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - ulpi_clk: impl Peripheral

> + 'd, - ulpi_dir: impl Peripheral

> + 'd, - ulpi_nxt: impl Peripheral

> + 'd, - ulpi_stp: impl Peripheral

> + 'd, - ulpi_d0: impl Peripheral

> + 'd, - ulpi_d1: impl Peripheral

> + 'd, - ulpi_d2: impl Peripheral

> + 'd, - ulpi_d3: impl Peripheral

> + 'd, - ulpi_d4: impl Peripheral

> + 'd, - ulpi_d5: impl Peripheral

> + 'd, - ulpi_d6: impl Peripheral

> + 'd, - ulpi_d7: impl Peripheral

> + 'd, + ulpi_clk: Peri<'d, impl UlpiClkPin>, + ulpi_dir: Peri<'d, impl UlpiDirPin>, + ulpi_nxt: Peri<'d, impl UlpiNxtPin>, + ulpi_stp: Peri<'d, impl UlpiStpPin>, + ulpi_d0: Peri<'d, impl UlpiD0Pin>, + ulpi_d1: Peri<'d, impl UlpiD1Pin>, + ulpi_d2: Peri<'d, impl UlpiD2Pin>, + ulpi_d3: Peri<'d, impl UlpiD3Pin>, + ulpi_d4: Peri<'d, impl UlpiD4Pin>, + ulpi_d5: Peri<'d, impl UlpiD5Pin>, + ulpi_d6: Peri<'d, impl UlpiD6Pin>, + ulpi_d7: Peri<'d, impl UlpiD7Pin>, ep_out_buffer: &'d mut [u8], config: Config, ) -> Self { @@ -129,8 +205,6 @@ impl<'d, T: Instance> Driver<'d, T> { ulpi_d7 ); - let regs = T::regs(); - let instance = OtgInstance { regs: T::regs(), state: T::state(), @@ -138,7 +212,6 @@ impl<'d, T: Instance> Driver<'d, T> { extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, endpoint_count: T::ENDPOINT_COUNT, phy_type: PhyType::ExternalHighSpeed, - quirk_setup_late_cnak: quirk_setup_late_cnak(regs), calculate_trdt_fn: calculate_trdt::, }; @@ -223,6 +296,33 @@ impl<'d, T: Instance> Bus<'d, T> { } }); + #[cfg(stm32h7rs)] + critical_section::with(|_| { + let rcc = crate::pac::RCC; + rcc.ahb1enr().modify(|w| { + w.set_usbphycen(true); + w.set_usb_otg_hsen(true); + }); + rcc.ahb1lpenr().modify(|w| { + w.set_usbphyclpen(true); + w.set_usb_otg_hslpen(true); + }); + }); + + #[cfg(all(stm32u5, peri_usb_otg_hs))] + { + crate::pac::SYSCFG.otghsphycr().modify(|w| { + w.set_en(true); + }); + + critical_section::with(|_| { + crate::pac::RCC.ahb2enr1().modify(|w| { + w.set_usb_otg_hsen(true); + w.set_usb_otg_hs_phyen(true); + }); + }); + } + let r = T::regs(); let core_id = r.cid().read().0; trace!("Core id {:08x}", core_id); @@ -235,8 +335,9 @@ impl<'d, T: Instance> Bus<'d, T> { // Configuring Vbus sense and SOF output match core_id { - 0x0000_1200 | 0x0000_1100 => self.inner.config_v1(), + 0x0000_1200 | 0x0000_1100 | 0x0000_1000 => self.inner.config_v1(), 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => self.inner.config_v2v3(), + 0x0000_5000 => self.inner.config_v5(), _ => unimplemented!("Unknown USB core id {:X}", core_id), } } @@ -306,7 +407,7 @@ trait SealedInstance { /// USB instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + 'static { +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static { /// Interrupt for this USB instance. type Interrupt: interrupt::typelevel::Interrupt; } @@ -448,11 +549,11 @@ foreach_interrupt!( ); fn calculate_trdt(speed: Dspd) -> u8 { - let ahb_freq = T::frequency().0; + let ahb_freq = T::bus_frequency().0; match speed { Dspd::HIGH_SPEED => { // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) - if ahb_freq >= 30_000_000 { + if ahb_freq >= 30_000_000 || cfg!(stm32h7rs) { 0x9 } else { panic!("AHB frequency is too low") @@ -477,7 +578,3 @@ fn calculate_trdt(speed: Dspd) -> u8 { _ => unimplemented!(), } } - -fn quirk_setup_late_cnak(r: Otg) -> bool { - r.cid().read().0 & 0xf000 == 0x1000 -} diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index 9384c8688..3e8e74a1f 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -5,7 +5,7 @@ use core::marker::PhantomData; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; -use embassy_hal_internal::into_ref; +use embassy_hal_internal::PeripheralType; use embassy_sync::waitqueue::AtomicWaker; use embassy_usb_driver as driver; use embassy_usb_driver::{ @@ -16,7 +16,7 @@ use crate::pac::usb::regs; use crate::pac::usb::vals::{EpType, Stat}; use crate::pac::USBRAM; use crate::rcc::RccPeripheral; -use crate::{interrupt, Peripheral}; +use crate::{interrupt, Peri}; /// Interrupt handler. pub struct InterruptHandler { @@ -80,8 +80,10 @@ impl interrupt::typelevel::Handler for InterruptHandl if istr.ctr() { let index = istr.ep_id() as usize; + let mut epr = regs.epr(index).read(); if epr.ctr_rx() { + RX_COMPLETE[index].store(true, Ordering::Relaxed); if index == 0 && epr.setup() { EP0_SETUP.store(true, Ordering::Relaxed); } @@ -89,6 +91,7 @@ impl interrupt::typelevel::Handler for InterruptHandl EP_OUT_WAKERS[index].wake(); } if epr.ctr_tx() { + TX_PENDING[index].store(false, Ordering::Relaxed); //trace!("EP {} TX", index); EP_IN_WAKERS[index].wake(); } @@ -117,11 +120,13 @@ const USBRAM_ALIGN: usize = 2; #[cfg(any(usbram_32_2048, usbram_32_1024))] const USBRAM_ALIGN: usize = 4; -const NEW_AW: AtomicWaker = AtomicWaker::new(); -static BUS_WAKER: AtomicWaker = NEW_AW; +static BUS_WAKER: AtomicWaker = AtomicWaker::new(); static EP0_SETUP: AtomicBool = AtomicBool::new(false); -static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; -static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; + +static TX_PENDING: [AtomicBool; EP_COUNT] = [const { AtomicBool::new(false) }; EP_COUNT]; +static RX_COMPLETE: [AtomicBool; EP_COUNT] = [const { AtomicBool::new(false) }; EP_COUNT]; +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; static IRQ_RESET: AtomicBool = AtomicBool::new(false); static IRQ_SUSPEND: AtomicBool = AtomicBool::new(false); static IRQ_RESUME: AtomicBool = AtomicBool::new(false); @@ -163,20 +168,37 @@ fn calc_out_len(len: u16) -> (u16, u16) { mod btable { use super::*; - pub(super) fn write_in(index: usize, addr: u16) { + pub(super) fn write_in_tx(index: usize, addr: u16) { USBRAM.mem(index * 4 + 0).write_value(addr); } - pub(super) fn write_in_len(index: usize, _addr: u16, len: u16) { + pub(super) fn write_in_rx(index: usize, addr: u16) { + USBRAM.mem(index * 4 + 2).write_value(addr); + } + + pub(super) fn write_in_len_rx(index: usize, _addr: u16, len: u16) { + USBRAM.mem(index * 4 + 3).write_value(len); + } + + pub(super) fn write_in_len_tx(index: usize, _addr: u16, len: u16) { USBRAM.mem(index * 4 + 1).write_value(len); } - pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { USBRAM.mem(index * 4 + 2).write_value(addr); USBRAM.mem(index * 4 + 3).write_value(max_len_bits); } - pub(super) fn read_out_len(index: usize) -> u16 { + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM.mem(index * 4 + 0).write_value(addr); + USBRAM.mem(index * 4 + 1).write_value(max_len_bits); + } + + pub(super) fn read_out_len_tx(index: usize) -> u16 { + USBRAM.mem(index * 4 + 1).read() + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { USBRAM.mem(index * 4 + 3).read() } } @@ -184,19 +206,35 @@ mod btable { mod btable { use super::*; - pub(super) fn write_in(_index: usize, _addr: u16) {} - - pub(super) fn write_in_len(index: usize, addr: u16, len: u16) { + pub(super) fn write_in_len_tx(index: usize, addr: u16, len: u16) { + assert_eq!(addr & 0b11, 0); USBRAM.mem(index * 2).write_value((addr as u32) | ((len as u32) << 16)); } - pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + pub(super) fn write_in_len_rx(index: usize, addr: u16, len: u16) { + assert_eq!(addr & 0b11, 0); + USBRAM + .mem(index * 2 + 1) + .write_value((addr as u32) | ((len as u32) << 16)); + } + + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM + .mem(index * 2) + .write_value((addr as u32) | ((max_len_bits as u32) << 16)); + } + + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { USBRAM .mem(index * 2 + 1) .write_value((addr as u32) | ((max_len_bits as u32) << 16)); } - pub(super) fn read_out_len(index: usize) -> u16 { + pub(super) fn read_out_len_tx(index: usize) -> u16 { + (USBRAM.mem(index * 2).read() >> 16) as u16 + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { (USBRAM.mem(index * 2 + 1).read() >> 16) as u16 } } @@ -249,15 +287,30 @@ pub struct Driver<'d, T: Instance> { } impl<'d, T: Instance> Driver<'d, T> { + /// Create a new USB driver with start-of-frame (SOF) output. + #[cfg(not(stm32l1))] + pub fn new_with_sof( + _usb: Peri<'d, T>, + _irq: impl interrupt::typelevel::Binding> + 'd, + dp: Peri<'d, impl DpPin>, + dm: Peri<'d, impl DmPin>, + sof: Peri<'d, impl SofPin>, + ) -> Self { + { + use crate::gpio::{AfType, OutputType, Speed}; + sof.set_as_af(sof.af_num(), AfType::output(OutputType::PushPull, Speed::VeryHigh)); + } + + Self::new(_usb, _irq, dp, dm) + } + /// Create a new USB driver. pub fn new( - _usb: impl Peripheral

+ 'd, + _usb: Peri<'d, T>, _irq: impl interrupt::typelevel::Binding> + 'd, - dp: impl Peripheral

> + 'd, - dm: impl Peripheral

> + 'd, + dp: Peri<'d, impl DpPin>, + dm: Peri<'d, impl DmPin>, ) -> Self { - into_ref!(dp, dm); - super::common_init::(); let regs = T::regs(); @@ -267,10 +320,8 @@ impl<'d, T: Instance> Driver<'d, T> { w.set_fres(true); }); - #[cfg(feature = "time")] - embassy_time::block_for(embassy_time::Duration::from_millis(100)); - #[cfg(not(feature = "time"))] - cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 10); + // wait t_STARTUP = 1us + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000_000); #[cfg(not(usb_v4))] regs.btable().write(|w| w.set_btable(0)); @@ -327,6 +378,14 @@ impl<'d, T: Instance> Driver<'d, T> { return false; // reserved for control pipe } let used = ep.used_out || ep.used_in; + if used && (ep.ep_type == EndpointType::Isochronous) { + // Isochronous endpoints are always double-buffered. + // Their corresponding endpoint/channel registers are forced to be unidirectional. + // Do not reuse this index. + // FIXME: Bulk endpoints can be double buffered, but are not in the current implementation. + return false; + } + let used_dir = match D::dir() { Direction::Out => ep.used_out, Direction::In => ep.used_in, @@ -350,7 +409,11 @@ impl<'d, T: Instance> Driver<'d, T> { let addr = self.alloc_ep_mem(len); trace!(" len_bits = {:04x}", len_bits); - btable::write_out::(index, addr, len_bits); + btable::write_out_rx::(index, addr, len_bits); + + if ep_type == EndpointType::Isochronous { + btable::write_out_tx::(index, addr, len_bits); + } EndpointBuffer { addr, @@ -365,8 +428,24 @@ impl<'d, T: Instance> Driver<'d, T> { let len = align_len_up(max_packet_size); let addr = self.alloc_ep_mem(len); - // ep_in_len is written when actually TXing packets. - btable::write_in::(index, addr); + #[cfg(not(any(usbram_32_2048, usbram_32_1024)))] + { + // ep_in_len is written when actually transmitting packets. + btable::write_in_tx::(index, addr); + + if ep_type == EndpointType::Isochronous { + btable::write_in_rx::(index, addr); + } + } + + #[cfg(any(usbram_32_2048, usbram_32_1024))] + { + btable::write_in_len_tx::(index, addr, 0); + + if ep_type == EndpointType::Isochronous { + btable::write_in_len_rx::(index, addr, 0); + } + } EndpointBuffer { addr, @@ -587,24 +666,27 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { } fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { - trace!("set_enabled {:x} {}", ep_addr, enabled); + trace!("set_enabled {:?} {}", ep_addr, enabled); // This can race, so do a retry loop. - let reg = T::regs().epr(ep_addr.index() as _); - trace!("EPR before: {:04x}", reg.read().0); + let epr = T::regs().epr(ep_addr.index() as _); + trace!("EPR before: {:04x}", epr.read().0); match ep_addr.direction() { Direction::In => { loop { let want_stat = match enabled { false => Stat::DISABLED, - true => Stat::NAK, + true => match epr.read().ep_type() { + EpType::ISO => Stat::VALID, + _ => Stat::NAK, + }, }; - let r = reg.read(); + let r = epr.read(); if r.stat_tx() == want_stat { break; } let mut w = invariant(r); w.set_stat_tx(Stat::from_bits(r.stat_tx().to_bits() ^ want_stat.to_bits())); - reg.write_value(w); + epr.write_value(w); } EP_IN_WAKERS[ep_addr.index()].wake(); } @@ -614,18 +696,18 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { false => Stat::DISABLED, true => Stat::VALID, }; - let r = reg.read(); + let r = epr.read(); if r.stat_rx() == want_stat { break; } let mut w = invariant(r); w.set_stat_rx(Stat::from_bits(r.stat_rx().to_bits() ^ want_stat.to_bits())); - reg.write_value(w); + epr.write_value(w); } EP_OUT_WAKERS[ep_addr.index()].wake(); } } - trace!("EPR after: {:04x}", reg.read().0); + trace!("EPR after: {:04x}", epr.read().0); } async fn enable(&mut self) {} @@ -656,6 +738,19 @@ impl Dir for Out { } } +/// Selects the packet buffer. +/// +/// For double-buffered endpoints, both the `Rx` and `Tx` buffer from a channel are used for the same +/// direction of transfer. This is opposed to single-buffered endpoints, where one channel can serve +/// two directions at the same time. +#[derive(Clone, Copy, Debug)] +enum PacketBuffer { + /// The RX buffer - must be used for single-buffered OUT endpoints + Rx, + /// The TX buffer - must be used for single-buffered IN endpoints + Tx, +} + /// USB endpoint. pub struct Endpoint<'d, T: Instance, D> { _phantom: PhantomData<(&'d mut T, D)>, @@ -664,15 +759,46 @@ pub struct Endpoint<'d, T: Instance, D> { } impl<'d, T: Instance, D> Endpoint<'d, T, D> { - fn write_data(&mut self, buf: &[u8]) { + /// Write to a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to write to the right counter field. + /// The DTOG_TX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next transmit packet will be stored, so we need to write the counter of the OTHER buffer, which is + /// where the last transmitted packet was stored. + fn write_data_double_buffered(&mut self, buf: &[u8], packet_buffer: PacketBuffer) { let index = self.info.addr.index(); self.buf.write(buf); - btable::write_in_len::(index, self.buf.addr, buf.len() as _); + + match packet_buffer { + PacketBuffer::Rx => btable::write_in_len_rx::(index, self.buf.addr, buf.len() as _), + PacketBuffer::Tx => btable::write_in_len_tx::(index, self.buf.addr, buf.len() as _), + } } - fn read_data(&mut self, buf: &mut [u8]) -> Result { + /// Write to a single-buffered endpoint. + fn write_data(&mut self, buf: &[u8]) { + self.write_data_double_buffered(buf, PacketBuffer::Tx); + } + + /// Read from a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to read from the right counter field. + /// The DTOG_RX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next received packet will be stored, so we need to read the counter of the OTHER buffer, which is + /// where the last received packet was stored. + fn read_data_double_buffered( + &mut self, + buf: &mut [u8], + packet_buffer: PacketBuffer, + ) -> Result { let index = self.info.addr.index(); - let rx_len = btable::read_out_len::(index) as usize & 0x3FF; + + let rx_len = match packet_buffer { + PacketBuffer::Rx => btable::read_out_len_rx::(index), + PacketBuffer::Tx => btable::read_out_len_tx::(index), + } as usize + & 0x3FF; + trace!("READ DONE, rx_len = {}", rx_len); if rx_len > buf.len() { return Err(EndpointError::BufferOverflow); @@ -680,6 +806,11 @@ impl<'d, T: Instance, D> Endpoint<'d, T, D> { self.buf.read(&mut buf[..rx_len]); Ok(rx_len) } + + /// Read from a single-buffered endpoint. + fn read_data(&mut self, buf: &mut [u8]) -> Result { + self.read_data_double_buffered(buf, PacketBuffer::Rx) + } } impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { @@ -734,29 +865,65 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { EP_OUT_WAKERS[index].register(cx.waker()); let regs = T::regs(); let stat = regs.epr(index).read().stat_rx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || RX_COMPLETE[index].load(Ordering::Relaxed) { + assert!(matches!(stat, Stat::VALID | Stat::DISABLED)); + Poll::Ready(stat) + } else { + Poll::Pending + } } else { - Poll::Pending + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } } }) .await; + // Errata for STM32H5, 2.20.1: + // During OUT transfers, the correct transfer interrupt (CTR) is triggered a little before the last USB SRAM accesses + // have completed. If the software responds quickly to the interrupt, the full buffer contents may not be correct. + // + // Workaround: + // Software should ensure that a small delay is included before accessing the SRAM contents. This delay should be + // 800 ns in Full Speed mode and 6.4 μs in Low Speed mode. + #[cfg(stm32h5)] + embassy_time::block_for(embassy_time::Duration::from_nanos(800)); + + RX_COMPLETE[index].store(false, Ordering::Relaxed); + if stat == Stat::DISABLED { return Err(EndpointError::Disabled); } - let rx_len = self.read_data(buf)?; - let regs = T::regs(); - regs.epr(index).write(|w| { - w.set_ep_type(convert_type(self.info.ep_type)); - w.set_ea(self.info.addr.index() as _); - w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); - w.set_stat_tx(Stat::from_bits(0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }); + + let rx_len = if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Read from the OTHER buffer. + let packet_buffer = if regs.epr(index).read().dtog_rx() { + PacketBuffer::Tx + } else { + PacketBuffer::Rx + }; + self.read_data_double_buffered(buf, packet_buffer)? + } else { + let len = self.read_data(buf)?; + + regs.epr(index).write(|w| { + w.set_ep_type(convert_type(self.info.ep_type)); + w.set_ea(self.info.addr.index() as _); + w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + w.set_stat_tx(Stat::from_bits(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + + len + }; trace!("READ OK, rx_len = {}", rx_len); Ok(rx_len) @@ -768,18 +935,41 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { if buf.len() > self.info.max_packet_size as usize { return Err(EndpointError::BufferOverflow); } + trace!("WRITE WAITING, buf.len() = {}", buf.len()); + let regs = T::regs(); let index = self.info.addr.index(); - trace!("WRITE WAITING"); + if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Write to the OTHER buffer. + let packet_buffer = if regs.epr(index).read().dtog_tx() { + PacketBuffer::Rx + } else { + PacketBuffer::Tx + }; + + self.write_data_double_buffered(buf, packet_buffer); + } + let stat = poll_fn(|cx| { EP_IN_WAKERS[index].register(cx.waker()); let regs = T::regs(); let stat = regs.epr(index).read().stat_tx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_TX` field to `NAK` after sending a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || !TX_PENDING[index].load(Ordering::Relaxed) { + assert!(matches!(stat, Stat::VALID | Stat::DISABLED)); + Poll::Ready(stat) + } else { + Poll::Pending + } } else { - Poll::Pending + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } } }) .await; @@ -788,18 +978,19 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { return Err(EndpointError::Disabled); } - self.write_data(buf); - - let regs = T::regs(); - regs.epr(index).write(|w| { - w.set_ep_type(convert_type(self.info.ep_type)); - w.set_ea(self.info.addr.index() as _); - w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); - w.set_stat_rx(Stat::from_bits(0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }); + if self.info.ep_type != EndpointType::Isochronous { + self.write_data(buf); + regs.epr(index).write(|w| { + w.set_ep_type(convert_type(self.info.ep_type)); + w.set_ea(self.info.addr.index() as _); + w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + w.set_stat_rx(Stat::from_bits(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + } + TX_PENDING[index].store(true, Ordering::Relaxed); trace!("WRITE OK"); Ok(()) @@ -1044,7 +1235,7 @@ trait SealedInstance { /// USB instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance + RccPeripheral + 'static { +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static { /// Interrupt for this USB instance. type Interrupt: interrupt::typelevel::Interrupt; } @@ -1052,6 +1243,7 @@ pub trait Instance: SealedInstance + RccPeripheral + 'static { // Internal PHY pins pin_trait!(DpPin, Instance); pin_trait!(DmPin, Instance); +pin_trait!(SofPin, Instance); foreach_interrupt!( ($inst:ident, usb, $block:ident, LP, $irq:ident) => { diff --git a/embassy-stm32/src/wdg/mod.rs b/embassy-stm32/src/wdg/mod.rs index ab21c4b6b..fb5c3d930 100644 --- a/embassy-stm32/src/wdg/mod.rs +++ b/embassy-stm32/src/wdg/mod.rs @@ -1,10 +1,11 @@ //! Watchdog Timer (IWDG, WWDG) use core::marker::PhantomData; -use embassy_hal_internal::{into_ref, Peripheral}; +use embassy_hal_internal::PeripheralType; use stm32_metapac::iwdg::vals::{Key, Pr}; use crate::rcc::LSI_FREQ; +use crate::Peri; /// Independent watchdog (IWDG) driver. pub struct IndependentWatchdog<'d, T: Instance> { @@ -29,9 +30,7 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> { /// /// [Self] has to be started with [Self::unleash()]. /// Once timer expires, MCU will be reset. To prevent this, timer must be reloaded by repeatedly calling [Self::pet()] within timeout interval. - pub fn new(_instance: impl Peripheral

+ 'd, timeout_us: u32) -> Self { - into_ref!(_instance); - + pub fn new(_instance: Peri<'d, T>, timeout_us: u32) -> Self { // Find lowest prescaler value, which makes watchdog period longer or equal to timeout. // This iterates from 4 (2^2) to 256 (2^8). let psc_power = unwrap!((2..=8).find(|psc_power| { @@ -86,7 +85,7 @@ trait SealedInstance { /// IWDG instance trait. #[allow(private_bounds)] -pub trait Instance: SealedInstance {} +pub trait Instance: SealedInstance + PeripheralType {} foreach_peripheral!( (iwdg, $inst:ident) => { diff --git a/embassy-stm32/src/xspi/enums.rs b/embassy-stm32/src/xspi/enums.rs new file mode 100644 index 000000000..c96641180 --- /dev/null +++ b/embassy-stm32/src/xspi/enums.rs @@ -0,0 +1,364 @@ +//! Enums used in Xspi configuration. +#[derive(Copy, Clone)] +pub(crate) enum XspiMode { + IndirectWrite, + IndirectRead, + #[expect(dead_code)] + AutoPolling, + #[expect(dead_code)] + MemoryMapped, +} + +impl Into for XspiMode { + fn into(self) -> u8 { + match self { + XspiMode::IndirectWrite => 0b00, + XspiMode::IndirectRead => 0b01, + XspiMode::AutoPolling => 0b10, + XspiMode::MemoryMapped => 0b11, + } + } +} + +/// Xspi lane width +#[derive(Copy, Clone)] +pub enum XspiWidth { + /// None + NONE, + /// Single lane + SING, + /// Dual lanes + DUAL, + /// Quad lanes + QUAD, + /// Eight lanes + OCTO, +} + +impl Into for XspiWidth { + fn into(self) -> u8 { + match self { + XspiWidth::NONE => 0b00, + XspiWidth::SING => 0b01, + XspiWidth::DUAL => 0b10, + XspiWidth::QUAD => 0b11, + XspiWidth::OCTO => 0b100, + } + } +} + +/// Wrap Size +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum WrapSize { + None, + _16Bytes, + _32Bytes, + _64Bytes, + _128Bytes, +} + +impl Into for WrapSize { + fn into(self) -> u8 { + match self { + WrapSize::None => 0x00, + WrapSize::_16Bytes => 0x02, + WrapSize::_32Bytes => 0x03, + WrapSize::_64Bytes => 0x04, + WrapSize::_128Bytes => 0x05, + } + } +} + +/// Memory Type +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum MemoryType { + Micron, + Macronix, + Standard, + MacronixRam, + HyperBusMemory, + HyperBusRegister, +} + +impl Into for MemoryType { + fn into(self) -> u8 { + match self { + MemoryType::Micron => 0x00, + MemoryType::Macronix => 0x01, + MemoryType::Standard => 0x02, + MemoryType::MacronixRam => 0x03, + MemoryType::HyperBusMemory => 0x04, + MemoryType::HyperBusRegister => 0x04, + } + } +} + +/// Xspi memory size. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum MemorySize { + _1KiB, + _2KiB, + _4KiB, + _8KiB, + _16KiB, + _32KiB, + _64KiB, + _128KiB, + _256KiB, + _512KiB, + _1MiB, + _2MiB, + _4MiB, + _8MiB, + _16MiB, + _32MiB, + _64MiB, + _128MiB, + _256MiB, + _512MiB, + _1GiB, + _2GiB, + _4GiB, + Other(u8), +} + +impl Into for MemorySize { + fn into(self) -> u8 { + match self { + MemorySize::_1KiB => 9, + MemorySize::_2KiB => 10, + MemorySize::_4KiB => 11, + MemorySize::_8KiB => 12, + MemorySize::_16KiB => 13, + MemorySize::_32KiB => 14, + MemorySize::_64KiB => 15, + MemorySize::_128KiB => 16, + MemorySize::_256KiB => 17, + MemorySize::_512KiB => 18, + MemorySize::_1MiB => 19, + MemorySize::_2MiB => 20, + MemorySize::_4MiB => 21, + MemorySize::_8MiB => 22, + MemorySize::_16MiB => 23, + MemorySize::_32MiB => 24, + MemorySize::_64MiB => 25, + MemorySize::_128MiB => 26, + MemorySize::_256MiB => 27, + MemorySize::_512MiB => 28, + MemorySize::_1GiB => 29, + MemorySize::_2GiB => 30, + MemorySize::_4GiB => 31, + MemorySize::Other(val) => val, + } + } +} + +/// Xspi Address size +#[derive(Copy, Clone)] +pub enum AddressSize { + /// 8-bit address + _8bit, + /// 16-bit address + _16bit, + /// 24-bit address + _24bit, + /// 32-bit address + _32bit, +} + +impl Into for AddressSize { + fn into(self) -> u8 { + match self { + AddressSize::_8bit => 0b00, + AddressSize::_16bit => 0b01, + AddressSize::_24bit => 0b10, + AddressSize::_32bit => 0b11, + } + } +} + +/// Time the Chip Select line stays high. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum ChipSelectHighTime { + _1Cycle, + _2Cycle, + _3Cycle, + _4Cycle, + _5Cycle, + _6Cycle, + _7Cycle, + _8Cycle, +} + +impl Into for ChipSelectHighTime { + fn into(self) -> u8 { + match self { + ChipSelectHighTime::_1Cycle => 0, + ChipSelectHighTime::_2Cycle => 1, + ChipSelectHighTime::_3Cycle => 2, + ChipSelectHighTime::_4Cycle => 3, + ChipSelectHighTime::_5Cycle => 4, + ChipSelectHighTime::_6Cycle => 5, + ChipSelectHighTime::_7Cycle => 6, + ChipSelectHighTime::_8Cycle => 7, + } + } +} + +/// FIFO threshold. +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum FIFOThresholdLevel { + _1Bytes, + _2Bytes, + _3Bytes, + _4Bytes, + _5Bytes, + _6Bytes, + _7Bytes, + _8Bytes, + _9Bytes, + _10Bytes, + _11Bytes, + _12Bytes, + _13Bytes, + _14Bytes, + _15Bytes, + _16Bytes, + _17Bytes, + _18Bytes, + _19Bytes, + _20Bytes, + _21Bytes, + _22Bytes, + _23Bytes, + _24Bytes, + _25Bytes, + _26Bytes, + _27Bytes, + _28Bytes, + _29Bytes, + _30Bytes, + _31Bytes, + _32Bytes, +} + +impl Into for FIFOThresholdLevel { + fn into(self) -> u8 { + match self { + FIFOThresholdLevel::_1Bytes => 0, + FIFOThresholdLevel::_2Bytes => 1, + FIFOThresholdLevel::_3Bytes => 2, + FIFOThresholdLevel::_4Bytes => 3, + FIFOThresholdLevel::_5Bytes => 4, + FIFOThresholdLevel::_6Bytes => 5, + FIFOThresholdLevel::_7Bytes => 6, + FIFOThresholdLevel::_8Bytes => 7, + FIFOThresholdLevel::_9Bytes => 8, + FIFOThresholdLevel::_10Bytes => 9, + FIFOThresholdLevel::_11Bytes => 10, + FIFOThresholdLevel::_12Bytes => 11, + FIFOThresholdLevel::_13Bytes => 12, + FIFOThresholdLevel::_14Bytes => 13, + FIFOThresholdLevel::_15Bytes => 14, + FIFOThresholdLevel::_16Bytes => 15, + FIFOThresholdLevel::_17Bytes => 16, + FIFOThresholdLevel::_18Bytes => 17, + FIFOThresholdLevel::_19Bytes => 18, + FIFOThresholdLevel::_20Bytes => 19, + FIFOThresholdLevel::_21Bytes => 20, + FIFOThresholdLevel::_22Bytes => 21, + FIFOThresholdLevel::_23Bytes => 22, + FIFOThresholdLevel::_24Bytes => 23, + FIFOThresholdLevel::_25Bytes => 24, + FIFOThresholdLevel::_26Bytes => 25, + FIFOThresholdLevel::_27Bytes => 26, + FIFOThresholdLevel::_28Bytes => 27, + FIFOThresholdLevel::_29Bytes => 28, + FIFOThresholdLevel::_30Bytes => 29, + FIFOThresholdLevel::_31Bytes => 30, + FIFOThresholdLevel::_32Bytes => 31, + } + } +} + +/// Dummy cycle count +#[allow(missing_docs)] +#[derive(Copy, Clone)] +pub enum DummyCycles { + _0, + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, + _17, + _18, + _19, + _20, + _21, + _22, + _23, + _24, + _25, + _26, + _27, + _28, + _29, + _30, + _31, +} + +impl Into for DummyCycles { + fn into(self) -> u8 { + match self { + DummyCycles::_0 => 0, + DummyCycles::_1 => 1, + DummyCycles::_2 => 2, + DummyCycles::_3 => 3, + DummyCycles::_4 => 4, + DummyCycles::_5 => 5, + DummyCycles::_6 => 6, + DummyCycles::_7 => 7, + DummyCycles::_8 => 8, + DummyCycles::_9 => 9, + DummyCycles::_10 => 10, + DummyCycles::_11 => 11, + DummyCycles::_12 => 12, + DummyCycles::_13 => 13, + DummyCycles::_14 => 14, + DummyCycles::_15 => 15, + DummyCycles::_16 => 16, + DummyCycles::_17 => 17, + DummyCycles::_18 => 18, + DummyCycles::_19 => 19, + DummyCycles::_20 => 20, + DummyCycles::_21 => 21, + DummyCycles::_22 => 22, + DummyCycles::_23 => 23, + DummyCycles::_24 => 24, + DummyCycles::_25 => 25, + DummyCycles::_26 => 26, + DummyCycles::_27 => 27, + DummyCycles::_28 => 28, + DummyCycles::_29 => 29, + DummyCycles::_30 => 30, + DummyCycles::_31 => 31, + } + } +} diff --git a/embassy-stm32/src/xspi/mod.rs b/embassy-stm32/src/xspi/mod.rs new file mode 100644 index 000000000..44c10b961 --- /dev/null +++ b/embassy-stm32/src/xspi/mod.rs @@ -0,0 +1,1425 @@ +//! XSPI Serial Peripheral Interface +//! + +#![macro_use] + +pub mod enums; + +use core::marker::PhantomData; + +use embassy_embedded_hal::{GetConfig, SetConfig}; +use embassy_hal_internal::PeripheralType; +pub use enums::*; + +use crate::dma::{word, ChannelAndRequest}; +use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::mode::{Async, Blocking, Mode as PeriMode}; +use crate::pac::xspi::vals::*; +use crate::pac::xspi::Xspi as Regs; +#[cfg(xspim_v1)] +use crate::pac::xspim::Xspim; +use crate::rcc::{self, RccPeripheral}; +use crate::{peripherals, Peri}; + +/// XPSI driver config. +#[derive(Clone, Copy)] +pub struct Config { + /// Fifo threshold used by the peripheral to generate the interrupt indicating data + /// or space is available in the FIFO + pub fifo_threshold: FIFOThresholdLevel, + /// Indicates the type of external device connected + pub memory_type: MemoryType, // Need to add an additional enum to provide this public interface + /// Defines the size of the external device connected to the XSPI corresponding + /// to the number of address bits required to access the device + pub device_size: MemorySize, + /// Sets the minimum number of clock cycles that the chip select signal must be held high + /// between commands + pub chip_select_high_time: ChipSelectHighTime, + /// Enables the free running clock + pub free_running_clock: bool, + /// Sets the clock level when the device is not selected + pub clock_mode: bool, + /// Indicates the wrap size corresponding to the external device configuration + pub wrap_size: WrapSize, + /// Specified the prescaler factor used for generating the external clock based + /// on the AHB clock. 0 = Fkernel, 1 = Fkernel/2, 2 = Fkernel/3 etc. + pub clock_prescaler: u8, + /// Allows the delay of 1/2 cycle the data sampling to account for external + /// signal delays + pub sample_shifting: bool, + /// Allows hold to 1/4 cycle the data + pub delay_hold_quarter_cycle: bool, + /// Enables the transaction boundary feature and defines the boundary to release + /// the chip select + pub chip_select_boundary: u8, + /// Enables communication regulation feature. Chip select is released when the other + /// XSpi requests access to the bus + pub max_transfer: u8, + /// Enables the refresh feature, chip select is released every refresh + 1 clock cycles + pub refresh: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + fifo_threshold: FIFOThresholdLevel::_16Bytes, // 32 bytes FIFO, half capacity + memory_type: MemoryType::Micron, + device_size: MemorySize::Other(0), + chip_select_high_time: ChipSelectHighTime::_5Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 0, + sample_shifting: false, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, // Acceptable range 0 to 31 + max_transfer: 0, + refresh: 0, + } + } +} + +/// XSPI transfer configuration. +pub struct TransferConfig { + /// Instruction width (IMODE) + pub iwidth: XspiWidth, + /// Instruction Id + pub instruction: Option, + /// Number of Instruction Bytes + pub isize: AddressSize, + /// Instruction Double Transfer rate enable + pub idtr: bool, + + /// Address width (ADMODE) + pub adwidth: XspiWidth, + /// Device memory address + pub address: Option, + /// Number of Address Bytes + pub adsize: AddressSize, + /// Address Double Transfer rate enable + pub addtr: bool, + + /// Alternate bytes width (ABMODE) + pub abwidth: XspiWidth, + /// Alternate Bytes + pub alternate_bytes: Option, + /// Number of Alternate Bytes + pub absize: AddressSize, + /// Alternate Bytes Double Transfer rate enable + pub abdtr: bool, + + /// Data width (DMODE) + pub dwidth: XspiWidth, + /// Data buffer + pub ddtr: bool, + + /// Number of dummy cycles (DCYC) + pub dummy: DummyCycles, +} + +impl Default for TransferConfig { + fn default() -> Self { + Self { + iwidth: XspiWidth::NONE, + instruction: None, + isize: AddressSize::_8bit, + idtr: false, + + adwidth: XspiWidth::NONE, + address: None, + adsize: AddressSize::_8bit, + addtr: false, + + abwidth: XspiWidth::NONE, + alternate_bytes: None, + absize: AddressSize::_8bit, + abdtr: false, + + dwidth: XspiWidth::NONE, + ddtr: false, + + dummy: DummyCycles::_0, + } + } +} + +/// Error used for Xspi implementation +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum XspiError { + /// Peripheral configuration is invalid + InvalidConfiguration, + /// Operation configuration is invalid + InvalidCommand, + /// Size zero buffer passed to instruction + EmptyBuffer, +} + +/// XSPI driver. +pub struct Xspi<'d, T: Instance, M: PeriMode> { + _peri: Peri<'d, T>, + clk: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + d11: Option>, + d12: Option>, + d13: Option>, + d14: Option>, + d15: Option>, + ncs: Option>, + // TODO: allow switching between multiple chips + ncs_alt: Option>, + // false if ncs == NCS1, true if ncs == NCS2 + // (ncs_alt will be the opposite to ncs). + _cssel_swap: bool, + dqs0: Option>, + dqs1: Option>, + dma: Option>, + _phantom: PhantomData, + config: Config, + width: XspiWidth, +} + +impl<'d, T: Instance, M: PeriMode> Xspi<'d, T, M> { + /// Enter memory mode. + /// The Input `read_config` is used to configure the read operation in memory mode + pub fn enable_memory_mapped_mode( + &mut self, + read_config: TransferConfig, + write_config: TransferConfig, + ) -> Result<(), XspiError> { + // Use configure command to set read config + self.configure_command(&read_config, None)?; + + let reg = T::REGS; + while reg.sr().read().busy() {} + + reg.ccr().modify(|r| { + r.set_dqse(false); + }); + + // Set wrting configurations, there are separate registers for write configurations in memory mapped mode + reg.wccr().modify(|w| { + w.set_imode(WccrImode::from_bits(write_config.iwidth.into())); + w.set_idtr(write_config.idtr); + w.set_isize(WccrIsize::from_bits(write_config.isize.into())); + + w.set_admode(WccrAdmode::from_bits(write_config.adwidth.into())); + w.set_addtr(write_config.addtr); + w.set_adsize(WccrAdsize::from_bits(write_config.adsize.into())); + + w.set_dmode(WccrDmode::from_bits(write_config.dwidth.into())); + w.set_ddtr(write_config.ddtr); + + w.set_abmode(WccrAbmode::from_bits(write_config.abwidth.into())); + w.set_dqse(true); + }); + + reg.wtcr().modify(|w| w.set_dcyc(write_config.dummy.into())); + + // Enable memory mapped mode + reg.cr().modify(|r| { + r.set_fmode(Fmode::B_0X3); + r.set_tcen(false); + }); + Ok(()) + } + + /// Quit from memory mapped mode + pub fn disable_memory_mapped_mode(&mut self) { + let reg = T::REGS; + + reg.cr().modify(|r| { + r.set_fmode(Fmode::B_0X0); + r.set_abort(true); + r.set_dmaen(false); + r.set_en(false); + }); + + // Clear transfer complete flag + reg.fcr().write(|w| w.set_ctcf(true)); + + // Re-enable ospi + reg.cr().modify(|r| { + r.set_en(true); + }); + } + + fn new_inner( + peri: Peri<'d, T>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + d4: Option>, + d5: Option>, + d6: Option>, + d7: Option>, + d8: Option>, + d9: Option>, + d10: Option>, + d11: Option>, + d12: Option>, + d13: Option>, + d14: Option>, + d15: Option>, + clk: Option>, + ncs_cssel: u8, + ncs: Option>, + ncs_alt: Option>, + dqs0: Option>, + dqs1: Option>, + dma: Option>, + config: Config, + width: XspiWidth, + dual_quad: bool, + ) -> Self { + // Enable the interface + match T::SPI_IDX { + 1 => crate::pac::PWR.csr2().modify(|r| r.set_en_xspim1(true)), + 2 => crate::pac::PWR.csr2().modify(|r| r.set_en_xspim2(true)), + _ => unreachable!(), + }; + + #[cfg(xspim_v1)] + { + // RCC for xspim should be enabled before writing register + crate::pac::RCC.ahb5enr().modify(|w| w.set_iomngren(true)); + + // Disable XSPI peripheral first + T::REGS.cr().modify(|w| { + w.set_en(false); + }); + + // XSPI IO Manager has been enabled before + T::SPIM_REGS.cr().modify(|w| { + w.set_muxen(false); + w.set_req2ack_time(0xff); + }); + } + + // System configuration + rcc::enable_and_reset::(); + while T::REGS.sr().read().busy() {} + + // Device configuration + T::REGS.dcr1().modify(|w| { + w.set_devsize(config.device_size.into()); + w.set_mtyp(Mtyp::from_bits(config.memory_type.into())); + w.set_csht(Csht::from_bits(config.chip_select_high_time.into())); + w.set_frck(false); + w.set_ckmode(Ckmode::from_bits(config.clock_mode.into())); + }); + + T::REGS.dcr2().modify(|w| { + w.set_wrapsize(Wrapsize::from_bits(config.wrap_size.into())); + }); + + T::REGS.dcr3().modify(|w| { + w.set_csbound(Csbound::from_bits(config.chip_select_boundary.into())); + #[cfg(xspi_v1)] + { + w.set_maxtran(Maxtran::from_bits(config.max_transfer.into())); + } + }); + + T::REGS.dcr4().modify(|w| { + w.set_refresh(Refresh::from_bits(config.refresh.into())); + }); + + T::REGS.cr().modify(|w| { + w.set_fthres(Fthres::from_bits(config.fifo_threshold.into())); + }); + + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + T::REGS.dcr2().modify(|w| { + w.set_prescaler(config.clock_prescaler); + }); + + // Wait for busy flag to clear after changing prescaler, during calibration + while T::REGS.sr().read().busy() {} + + T::REGS.cr().modify(|w| { + w.set_dmm(dual_quad); + + assert!(ncs_alt.is_none(), "ncs_alt TODO"); + let cssel = if ncs_cssel == 0 { Cssel::B_0X0 } else { Cssel::B_0X1 }; + w.set_cssel(cssel); + }); + + T::REGS.tcr().modify(|w| { + w.set_sshift(config.sample_shifting); + w.set_dhqc(config.delay_hold_quarter_cycle); + }); + + // Enable peripheral + T::REGS.cr().modify(|w| { + w.set_en(true); + }); + + // Free running clock needs to be set after peripheral enable + if config.free_running_clock { + T::REGS.dcr1().modify(|w| { + w.set_frck(config.free_running_clock); + }); + } + + Self { + _peri: peri, + clk, + d0, + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + d15, + ncs, + ncs_alt, + _cssel_swap: ncs_cssel == 1, + dqs0, + dqs1, + dma, + _phantom: PhantomData, + config, + width, + } + } + + // Function to configure the peripheral for the requested command + fn configure_command(&mut self, command: &TransferConfig, data_len: Option) -> Result<(), XspiError> { + // Check that transaction doesn't use more than hardware initialized pins + if >::into(command.iwidth) > >::into(self.width) + || >::into(command.adwidth) > >::into(self.width) + || >::into(command.abwidth) > >::into(self.width) + || >::into(command.dwidth) > >::into(self.width) + { + return Err(XspiError::InvalidCommand); + } + + T::REGS.cr().modify(|w| { + w.set_fmode(0.into()); + }); + + // Configure alternate bytes + if let Some(ab) = command.alternate_bytes { + T::REGS.abr().write(|v| v.set_alternate(ab)); + T::REGS.ccr().modify(|w| { + w.set_abmode(CcrAbmode::from_bits(command.abwidth.into())); + w.set_abdtr(command.abdtr); + w.set_absize(CcrAbsize::from_bits(command.absize.into())); + }) + } + + // Configure dummy cycles + T::REGS.tcr().modify(|w| { + w.set_dcyc(command.dummy.into()); + }); + + // Configure data + if let Some(data_length) = data_len { + T::REGS.dlr().write(|v| { + v.set_dl((data_length - 1) as u32); + }) + } else { + T::REGS.dlr().write(|v| { + v.set_dl((0) as u32); + }) + } + + // Configure instruction/address/data modes + T::REGS.ccr().modify(|w| { + w.set_imode(CcrImode::from_bits(command.iwidth.into())); + w.set_idtr(command.idtr); + w.set_isize(CcrIsize::from_bits(command.isize.into())); + + w.set_admode(CcrAdmode::from_bits(command.adwidth.into())); + w.set_addtr(command.addtr); + w.set_adsize(CcrAdsize::from_bits(command.adsize.into())); + + w.set_dmode(CcrDmode::from_bits(command.dwidth.into())); + w.set_ddtr(command.ddtr); + }); + + // Set information required to initiate transaction + if let Some(instruction) = command.instruction { + if let Some(address) = command.address { + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + // Double check requirements for delay hold and sample shifting + // if let None = command.data_len { + // if self.config.delay_hold_quarter_cycle && command.idtr { + // T::REGS.ccr().modify(|w| { + // w.set_ddtr(true); + // }); + // } + // } + + // warn!("instruction: {:#x}", instruction); + T::REGS.ir().write(|v| { + v.set_instruction(instruction); + }); + } + } else { + if let Some(address) = command.address { + T::REGS.ar().write(|v| { + v.set_address(address); + }); + } else { + // The only single phase transaction supported is instruction only + return Err(XspiError::InvalidCommand); + } + } + + Ok(()) + } + + /// Function used to control or configure the target device without data transfer + pub fn blocking_command(&mut self, command: &TransferConfig) -> Result<(), XspiError> { + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Need additional validation that command configuration doesn't have data set + self.configure_command(command, None)?; + + // Transaction initiated by setting final configuration, i.e the instruction register + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|w| { + w.set_ctcf(true); + }); + + Ok(()) + } + + /// Blocking read with byte by byte data transfer + pub fn blocking_read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), XspiError> { + if buf.is_empty() { + return Err(XspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + // Ensure DMA is not enabled for this transaction + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + // self.configure_command(&transaction, Some(buf.len()))?; + self.configure_command(&transaction, Some(buf.len())).unwrap(); + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectRead.into()))); + if T::REGS.ccr().read().admode() == CcrAdmode::B_0X0 { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + for idx in 0..buf.len() { + while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + buf[idx] = unsafe { (T::REGS.dr().as_ptr() as *mut W).read_volatile() }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Blocking write with byte by byte data transfer + pub fn blocking_write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), XspiError> { + if buf.is_empty() { + return Err(XspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + self.configure_command(&transaction, Some(buf.len()))?; + + T::REGS + .cr() + .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); + + for idx in 0..buf.len() { + while !T::REGS.sr().read().ftf() {} + unsafe { (T::REGS.dr().as_ptr() as *mut W).write_volatile(buf[idx]) }; + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().write(|v| v.set_ctcf(true)); + + Ok(()) + } + + /// Set new bus configuration + pub fn set_config(&mut self, config: &Config) { + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + // Disable DMA channel while configuring the peripheral + T::REGS.cr().modify(|w| { + w.set_dmaen(false); + }); + + // Device configuration + T::REGS.dcr1().modify(|w| { + w.set_devsize(config.device_size.into()); + w.set_mtyp(Mtyp::from_bits(config.memory_type.into())); + w.set_csht(Csht::from_bits(config.chip_select_high_time.into())); + w.set_frck(false); + w.set_ckmode(match config.clock_mode { + true => Ckmode::B_0X1, + false => Ckmode::B_0X0, + }); + }); + + T::REGS.dcr2().modify(|w| { + w.set_wrapsize(Wrapsize::from_bits(config.wrap_size.into())); + }); + + T::REGS.dcr3().modify(|w| { + w.set_csbound(Csbound::from_bits(config.chip_select_boundary.into())); + #[cfg(xspi_v1)] + { + w.set_maxtran(Maxtran::from_bits(config.max_transfer.into())); + } + }); + + T::REGS.dcr4().modify(|w| { + w.set_refresh(Refresh::from_bits(config.refresh.into())); + }); + + T::REGS.cr().modify(|w| { + w.set_fthres(Fthres::from_bits(config.fifo_threshold.into())); + }); + + // Wait for busy flag to clear + while T::REGS.sr().read().busy() {} + + T::REGS.dcr2().modify(|w| { + w.set_prescaler(config.clock_prescaler); + }); + + T::REGS.tcr().modify(|w| { + w.set_sshift(config.sample_shifting); + w.set_dhqc(config.delay_hold_quarter_cycle); + }); + + // Enable peripheral + T::REGS.cr().modify(|w| { + w.set_en(true); + }); + + // Free running clock needs to be set after peripheral enable + if config.free_running_clock { + T::REGS.dcr1().modify(|w| { + w.set_frck(config.free_running_clock); + }); + } + + self.config = *config; + } + + /// Get current configuration + pub fn get_config(&self) -> Config { + self.config + } +} + +impl<'d, T: Instance> Xspi<'d, T, Blocking> { + /// Create new blocking XSPI driver for a single spi external chip + pub fn new_blocking_singlespi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + ncs: Peri<'d, impl NCSEither>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + None, + config, + XspiWidth::SING, + false, + ) + } + + /// Create new blocking XSPI driver for a dualspi external chip + pub fn new_blocking_dualspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + ncs: Peri<'d, impl NCSEither>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + None, + config, + XspiWidth::DUAL, + false, + ) + } + + /// Create new blocking XSPI driver for a quadspi external chip + pub fn new_blocking_quadspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + ncs: Peri<'d, impl NCSEither>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + None, + config, + XspiWidth::QUAD, + false, + ) + } + + /// Create new blocking XSPI driver for two quadspi external chips + pub fn new_blocking_dualquadspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + ncs: Peri<'d, impl NCSEither>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + None, + config, + XspiWidth::QUAD, + true, + ) + } + + /// Create new blocking XSPI driver for xspi external chips + pub fn new_blocking_xspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + ncs: Peri<'d, impl NCSEither>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + None, + config, + XspiWidth::OCTO, + false, + ) + } +} + +impl<'d, T: Instance> Xspi<'d, T, Async> { + /// Create new blocking XSPI driver for a single spi external chip + pub fn new_singlespi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + ncs: Peri<'d, impl NCSEither>, + dma: Peri<'d, impl XDma>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::input(Pull::None)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + new_dma!(dma), + config, + XspiWidth::SING, + false, + ) + } + + /// Create new blocking XSPI driver for a dualspi external chip + pub fn new_dualspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + ncs: Peri<'d, impl NCSEither>, + dma: Peri<'d, impl XDma>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + new_dma!(dma), + config, + XspiWidth::DUAL, + false, + ) + } + + /// Create new blocking XSPI driver for a quadspi external chip + pub fn new_quadspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + ncs: Peri<'d, impl NCSEither>, + dma: Peri<'d, impl XDma>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + new_dma!(dma), + config, + XspiWidth::QUAD, + false, + ) + } + + /// Create new blocking XSPI driver for two quadspi external chips + pub fn new_dualquadspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + ncs: Peri<'d, impl NCSEither>, + dma: Peri<'d, impl XDma>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + new_dma!(dma), + config, + XspiWidth::QUAD, + true, + ) + } + + /// Create new blocking XSPI driver for xspi external chips + pub fn new_xspi( + peri: Peri<'d, T>, + clk: Peri<'d, impl CLKPin>, + d0: Peri<'d, impl D0Pin>, + d1: Peri<'d, impl D1Pin>, + d2: Peri<'d, impl D2Pin>, + d3: Peri<'d, impl D3Pin>, + d4: Peri<'d, impl D4Pin>, + d5: Peri<'d, impl D5Pin>, + d6: Peri<'d, impl D6Pin>, + d7: Peri<'d, impl D7Pin>, + ncs: Peri<'d, impl NCSEither>, + dma: Peri<'d, impl XDma>, + config: Config, + ) -> Self { + Self::new_inner( + peri, + new_pin!(d0, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d1, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d2, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d3, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d4, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d5, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d6, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + new_pin!(d7, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + None, + None, + None, + None, + None, + None, + None, + None, + new_pin!(clk, AfType::output(OutputType::PushPull, Speed::VeryHigh)), + ncs.sel(), + new_pin!( + ncs, + AfType::output_pull(OutputType::PushPull, Speed::VeryHigh, Pull::Up) + ), + None, + None, + None, + new_dma!(dma), + config, + XspiWidth::OCTO, + false, + ) + } + + /// Blocking read with DMA transfer + pub fn blocking_read_dma(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), XspiError> { + if buf.is_empty() { + return Err(XspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectRead.into()))); + if T::REGS.ccr().read().admode() == CcrAdmode::B_0X0 { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Blocking write with DMA transfer + pub fn blocking_write_dma(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), XspiError> { + if buf.is_empty() { + return Err(XspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.blocking_wait(); + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous read from external device + pub async fn read(&mut self, buf: &mut [W], transaction: TransferConfig) -> Result<(), XspiError> { + if buf.is_empty() { + return Err(XspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + + let current_address = T::REGS.ar().read().address(); + let current_instruction = T::REGS.ir().read().instruction(); + + // For a indirect read transaction, the transaction begins when the instruction/address is set + T::REGS + .cr() + .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectRead.into()))); + if T::REGS.ccr().read().admode() == CcrAdmode::B_0X0 { + T::REGS.ir().write(|v| v.set_instruction(current_instruction)); + } else { + T::REGS.ar().write(|v| v.set_address(current_address)); + } + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .read(T::REGS.dr().as_ptr() as *mut W, buf, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } + + /// Asynchronous write to external device + pub async fn write(&mut self, buf: &[W], transaction: TransferConfig) -> Result<(), XspiError> { + if buf.is_empty() { + return Err(XspiError::EmptyBuffer); + } + + // Wait for peripheral to be free + while T::REGS.sr().read().busy() {} + + self.configure_command(&transaction, Some(buf.len()))?; + T::REGS + .cr() + .modify(|v| v.set_fmode(Fmode::from_bits(XspiMode::IndirectWrite.into()))); + + let transfer = unsafe { + self.dma + .as_mut() + .unwrap() + .write(buf, T::REGS.dr().as_ptr() as *mut W, Default::default()) + }; + + T::REGS.cr().modify(|w| w.set_dmaen(true)); + + transfer.await; + + finish_dma(T::REGS); + + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> Drop for Xspi<'d, T, M> { + fn drop(&mut self) { + self.clk.as_ref().map(|x| x.set_as_disconnected()); + self.d0.as_ref().map(|x| x.set_as_disconnected()); + self.d1.as_ref().map(|x| x.set_as_disconnected()); + self.d2.as_ref().map(|x| x.set_as_disconnected()); + self.d3.as_ref().map(|x| x.set_as_disconnected()); + self.d4.as_ref().map(|x| x.set_as_disconnected()); + self.d5.as_ref().map(|x| x.set_as_disconnected()); + self.d6.as_ref().map(|x| x.set_as_disconnected()); + self.d7.as_ref().map(|x| x.set_as_disconnected()); + self.d8.as_ref().map(|x| x.set_as_disconnected()); + self.d9.as_ref().map(|x| x.set_as_disconnected()); + self.d10.as_ref().map(|x| x.set_as_disconnected()); + self.d11.as_ref().map(|x| x.set_as_disconnected()); + self.d12.as_ref().map(|x| x.set_as_disconnected()); + self.d13.as_ref().map(|x| x.set_as_disconnected()); + self.d14.as_ref().map(|x| x.set_as_disconnected()); + self.d15.as_ref().map(|x| x.set_as_disconnected()); + self.ncs.as_ref().map(|x| x.set_as_disconnected()); + self.ncs_alt.as_ref().map(|x| x.set_as_disconnected()); + self.dqs0.as_ref().map(|x| x.set_as_disconnected()); + self.dqs1.as_ref().map(|x| x.set_as_disconnected()); + + rcc::disable::(); + } +} + +fn finish_dma(regs: Regs) { + while !regs.sr().read().tcf() {} + regs.fcr().write(|v| v.set_ctcf(true)); + + regs.cr().modify(|w| { + w.set_dmaen(false); + }); +} + +/// XSPI I/O manager instance trait. +#[cfg(xspim_v1)] +pub(crate) trait SealedXspimInstance { + const SPIM_REGS: Xspim; + const SPI_IDX: u8; +} + +/// XSPI instance trait. +pub(crate) trait SealedInstance { + const REGS: Regs; +} + +/// XSPI instance trait. +#[cfg(xspim_v1)] +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + SealedXspimInstance {} + +/// XSPI instance trait. +#[cfg(not(xspim_v1))] +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + RccPeripheral {} + +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(D4Pin, Instance); +pin_trait!(D5Pin, Instance); +pin_trait!(D6Pin, Instance); +pin_trait!(D7Pin, Instance); +pin_trait!(D8Pin, Instance); +pin_trait!(D9Pin, Instance); +pin_trait!(D10Pin, Instance); +pin_trait!(D11Pin, Instance); +pin_trait!(D12Pin, Instance); +pin_trait!(D13Pin, Instance); +pin_trait!(D14Pin, Instance); +pin_trait!(D15Pin, Instance); +pin_trait!(DQS0Pin, Instance); +pin_trait!(DQS1Pin, Instance); +pin_trait!(NCSPin, Instance); +pin_trait!(CLKPin, Instance); +pin_trait!(NCLKPin, Instance); +dma_trait!(XDma, Instance); + +/// Trait for either NCS1 or NCS2 pins +pub trait NCSEither: NCSPin { + /// Get the CSSEL for this NCS pin + fn sel(&self) -> u8; +} + +// Hard-coded the xspi index, for SPIM +#[cfg(xspim_v1)] +impl SealedXspimInstance for peripherals::XSPI1 { + const SPIM_REGS: Xspim = crate::pac::XSPIM; + const SPI_IDX: u8 = 1; +} + +// Some cubedb files are missing XSPI2, for example STM32H7R3Z8 +#[cfg(all(xspim_v1, peri_xspi2))] +impl SealedXspimInstance for peripherals::XSPI2 { + const SPIM_REGS: Xspim = crate::pac::XSPIM; + const SPI_IDX: u8 = 2; +} + +#[cfg(xspim_v1)] +foreach_peripheral!( + (xspi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +#[cfg(not(xspim_v1))] +foreach_peripheral!( + (xspi, $inst:ident) => { + impl SealedInstance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); + +impl<'d, T: Instance, M: PeriMode> SetConfig for Xspi<'d, T, M> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.set_config(config); + Ok(()) + } +} + +impl<'d, T: Instance, M: PeriMode> GetConfig for Xspi<'d, T, M> { + type Config = Config; + fn get_config(&self) -> Self::Config { + self.get_config() + } +} + +/// Word sizes usable for XSPI. +#[allow(private_bounds)] +pub trait Word: word::Word {} + +macro_rules! impl_word { + ($T:ty) => { + impl Word for $T {} + }; +} + +impl_word!(u8); +impl_word!(u16); +impl_word!(u32); diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index 8f2d26fe0..89684de0c 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -7,7 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- Add LazyLock sync primitive. +## 0.7.0 - 2025-05-28 + +- Add `remove_if` to `priority_channel::{Receiver, PriorityChannel}`. +- impl `Stream` for `channel::{Receiver, Channel}`. +- Fix channels to wake senders on `clear()`. + For `Channel`, `PriorityChannel`, `PubSub`, `zerocopy_channel::Channel`. +- Allow `zerocopy_channel::Channel` to auto-implement `Sync`/`Send`. +- Add `must_use` to `MutexGuard`. +- Add a `RwLock`. +- Add `lock_mut` to `blocking_mutex::Mutex`. +- Don't select a critical-section implementation when `std` feature is enabled. +- Improve waker documentation. +- Improve `Signal` and `Watch` documentation. +- Update to defmt 1.0. This remains compatible with latest defmt 0.3. +- Add `peek` method on `channel` and `priority_channel`. +- Add dynamic sender and receiver that are Send + Sync for `channel`. + +## 0.6.2 - 2025-01-15 + +- Add dynamic dispatch variant of `Pipe`. + +## 0.6.1 - 2024-11-22 + +- Add `LazyLock` sync primitive. +- Add `Watch` sync primitive. +- Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `priority_channel::{Sender, Receiver}`. +- Add `GenericAtomicWaker` utility. ## 0.6.0 - 2024-05-29 @@ -16,20 +44,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `PubSubChannel`. - Made `PubSubBehavior` sealed - If you called `.publish_immediate(...)` on the queue directly before, then now call `.immediate_publisher().publish_immediate(...)` -- Add OnceLock sync primitive. -- Add constructor for DynamicChannel -- Add ready_to_receive functions to Channel and Receiver. +- Add `OnceLock` sync primitive. +- Add constructor for `DynamicChannel` +- Add ready_to_receive functions to `Channel` and `Receiver`. ## 0.5.0 - 2023-12-04 -- Add a PriorityChannel. -- Remove nightly and unstable-traits features in preparation for 1.75. -- Upgrade heapless to 0.8. -- Upgrade static-cell to 2.0. +- Add a `PriorityChannel`. +- Remove `nightly` and `unstable-traits` features in preparation for 1.75. +- Upgrade `heapless` to 0.8. +- Upgrade `static-cell` to 2.0. ## 0.4.0 - 2023-10-31 -- Re-add impl_trait_projections +- Re-add `impl_trait_projections` - switch to `embedded-io 0.6` ## 0.3.0 - 2023-09-14 diff --git a/embassy-sync/Cargo.toml b/embassy-sync/Cargo.toml index 7b7d2bf8e..99962f9f6 100644 --- a/embassy-sync/Cargo.toml +++ b/embassy-sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-sync" -version = "0.6.0" +version = "0.7.0" edition = "2021" description = "no-std, no-alloc synchronization primitives with async support" repository = "https://github.com/embassy-rs/embassy" @@ -20,13 +20,14 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-sync/ target = "thumbv7em-none-eabi" [features] -std = ["critical-section/std"] +std = [] turbowakers = [] [dependencies] -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } +futures-sink = { version = "0.3", default-features = false, features = [] } futures-util = { version = "0.3.17", default-features = false } critical-section = "1.1" heapless = "0.8" @@ -37,7 +38,7 @@ embedded-io-async = { version = "0.6.1" } futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } futures-test = "0.3.17" futures-timer = "3.0.2" -futures-util = { version = "0.3.17", features = [ "channel" ] } +futures-util = { version = "0.3.17", features = [ "channel", "sink" ] } # Enable critical-section implementation for std, for tests critical-section = { version = "1.1", features = ["std"] } diff --git a/embassy-sync/README.md b/embassy-sync/README.md index dec1fbc32..91c59884f 100644 --- a/embassy-sync/README.md +++ b/embassy-sync/README.md @@ -5,13 +5,14 @@ An [Embassy](https://embassy.dev) project. Synchronization primitives and data structures with async support: - [`Channel`](channel::Channel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. -- [`PriorityChannel`](channel::priority::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. +- [`PriorityChannel`](priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. - [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. - [`Signal`](signal::Signal) - Signalling latest value to a single consumer. +- [`Watch`](watch::Watch) - Signalling latest value to multiple consumers. - [`Mutex`](mutex::Mutex) - Mutex for synchronizing state between asynchronous tasks. - [`Pipe`](pipe::Pipe) - Byte stream implementing `embedded_io` traits. - [`WakerRegistration`](waitqueue::WakerRegistration) - Utility to register and wake a `Waker`. -- [`AtomicWaker`](waitqueue::AtomicWaker) - A variant of `WakerRegistration` accessible using a non-mut API. +- [`AtomicWaker`](waitqueue::AtomicWaker) - Utility to register and wake a `Waker` from interrupt context. - [`MultiWakerRegistration`](waitqueue::MultiWakerRegistration) - Utility registering and waking multiple `Waker`'s. - [`LazyLock`](lazy_lock::LazyLock) - A value which is initialized on the first access diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs index 8a4a4c642..a41bc3569 100644 --- a/embassy-sync/src/blocking_mutex/mod.rs +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -50,6 +50,23 @@ impl Mutex { f(inner) }) } + + /// Creates a critical section and grants temporary mutable access to the protected data. + /// + /// # Safety + /// + /// This method is marked unsafe because calling this method re-entrantly, i.e. within + /// another `lock_mut` or `lock` closure, violates Rust's aliasing rules. Calling this + /// method at the same time from different tasks is safe. For a safe alternative with + /// mutable access that never causes UB, use a `RefCell` in a `Mutex`. + pub unsafe fn lock_mut(&self, f: impl FnOnce(&mut T) -> U) -> U { + self.raw.lock(|| { + let ptr = self.data.get() as *mut T; + // Safety: we have exclusive access to the data, as long as this mutex is not locked re-entrantly + let inner = unsafe { &mut *ptr }; + f(inner) + }) + } } impl Mutex { @@ -104,6 +121,7 @@ impl Mutex { impl Mutex { /// Borrows the data + #[allow(clippy::should_implement_trait)] pub fn borrow(&self) -> &T { let ptr = self.data.get() as *const T; unsafe { &*ptr } diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 55ac5fb66..856551417 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -72,6 +72,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Send-only access to a [`Channel`] without knowing channel size. @@ -122,6 +164,57 @@ impl<'ch, T> DynamicSender<'ch, T> { } } +/// Send-only access to a [`Channel`] without knowing channel size. +/// This version can be sent between threads but can only be created if the underlying mutex is Sync. +pub struct SendDynamicSender<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for SendDynamicSender<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for SendDynamicSender<'ch, T> {} +unsafe impl<'ch, T: Send> Send for SendDynamicSender<'ch, T> {} +unsafe impl<'ch, T: Send> Sync for SendDynamicSender<'ch, T> {} + +impl<'ch, M, T, const N: usize> From> for SendDynamicSender<'ch, T> +where + M: RawMutex + Sync + Send, +{ + fn from(s: Sender<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + +impl<'ch, T> SendDynamicSender<'ch, T> { + /// Sends a value. + /// + /// See [`Channel::send()`] + pub fn send(&self, message: T) -> DynamicSendFuture<'ch, T> { + DynamicSendFuture { + channel: self.channel, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// See [`Channel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send_with_context(message, None) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } +} + /// Receive-only access to a [`Channel`]. pub struct Receiver<'ch, M, T, const N: usize> where @@ -166,6 +259,16 @@ where self.channel.try_receive() } + /// Peek at the next value without removing it from the queue. + /// + /// See [`Channel::try_peek()`] + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.channel.try_peek() + } + /// Allows a poll_fn to poll until the channel is ready to receive /// /// See [`Channel::poll_ready_to_receive()`] @@ -179,6 +282,48 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } /// Receive-only access to a [`Channel`] without knowing channel size. @@ -209,6 +354,16 @@ impl<'ch, T> DynamicReceiver<'ch, T> { self.channel.try_receive_with_context(None) } + /// Peek at the next value without removing it from the queue. + /// + /// See [`Channel::try_peek()`] + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.channel.try_peek_with_context(None) + } + /// Allows a poll_fn to poll until the channel is ready to receive /// /// See [`Channel::poll_ready_to_receive()`] @@ -233,6 +388,72 @@ where } } +/// Receive-only access to a [`Channel`] without knowing channel size. +/// This version can be sent between threads but can only be created if the underlying mutex is Sync. +pub struct SendDynamicReceiver<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for SendDynamicReceiver<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for SendDynamicReceiver<'ch, T> {} +unsafe impl<'ch, T: Send> Send for SendDynamicReceiver<'ch, T> {} +unsafe impl<'ch, T: Send> Sync for SendDynamicReceiver<'ch, T> {} + +impl<'ch, T> SendDynamicReceiver<'ch, T> { + /// Receive the next value. + /// + /// See [`Channel::receive()`]. + pub fn receive(&self) -> DynamicReceiveFuture<'_, T> { + DynamicReceiveFuture { channel: self.channel } + } + + /// Attempt to immediately receive the next value. + /// + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive_with_context(None) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +impl<'ch, M, T, const N: usize> From> for SendDynamicReceiver<'ch, T> +where + M: RawMutex + Sync + Send, +{ + fn from(s: Receiver<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + +impl<'ch, M, T, const N: usize> futures_util::Stream for Receiver<'ch, M, T, N> +where + M: RawMutex, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.channel.poll_receive(cx).map(Some) + } +} + /// Future returned by [`Channel::receive`] and [`Receiver::receive`]. #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct ReceiveFuture<'ch, M, T, const N: usize> @@ -368,6 +589,10 @@ pub(crate) trait DynamicChannel { fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result; + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone; + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()>; fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()>; @@ -410,6 +635,31 @@ impl ChannelState { self.try_receive_with_context(None) } + fn try_peek(&mut self) -> Result + where + T: Clone, + { + self.try_peek_with_context(None) + } + + fn try_peek_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + if self.queue.is_full() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.front() { + Ok(message.clone()) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { if self.queue.is_full() { self.senders_waker.wake(); @@ -478,6 +728,9 @@ impl ChannelState { } fn clear(&mut self) { + if self.queue.is_full() { + self.senders_waker.wake(); + } self.queue.clear(); } @@ -536,6 +789,13 @@ where self.lock(|c| c.try_receive_with_context(cx)) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek_with_context(cx)) + } + /// Poll the channel for the next message pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.lock(|c| c.poll_receive(cx)) @@ -624,6 +884,17 @@ where self.lock(|c| c.try_receive()) } + /// Peek at the next value without removing it from the queue. + /// + /// This method will either receive a copy of the message from the channel immediately or return + /// an error if the channel is empty. + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek()) + } + /// Returns the maximum number of elements the channel can hold. pub const fn capacity(&self) -> usize { N @@ -671,6 +942,13 @@ where Channel::try_receive_with_context(self, cx) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + Channel::try_peek_with_context(self, cx) + } + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { Channel::poll_ready_to_send(self, cx) } @@ -684,6 +962,17 @@ where } } +impl futures_util::Stream for Channel +where + M: RawMutex, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_receive(cx).map(Some) + } +} + #[cfg(test)] mod tests { use core::time::Duration; @@ -742,6 +1031,8 @@ mod tests { fn simple_send_and_receive() { let c = Channel::::new(); assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_peek().unwrap(), 1); + assert_eq!(c.try_peek().unwrap(), 1); assert_eq!(c.try_receive().unwrap(), 1); } @@ -772,6 +1063,8 @@ mod tests { let r = c.dyn_receiver(); assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_peek().unwrap(), 1); + assert_eq!(r.try_peek().unwrap(), 1); assert_eq!(r.try_receive().unwrap(), 1); } diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index 014bf1d06..5d91b4d9c 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -18,7 +18,9 @@ pub mod once_lock; pub mod pipe; pub mod priority_channel; pub mod pubsub; +pub mod rwlock; pub mod semaphore; pub mod signal; pub mod waitqueue; +pub mod watch; pub mod zerocopy_channel; diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 8c3a3af9f..7528a9f68 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -2,7 +2,7 @@ //! //! This module provides a mutex that can be used to synchronize data between asynchronous tasks. use core::cell::{RefCell, UnsafeCell}; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::ops::{Deref, DerefMut}; use core::task::Poll; use core::{fmt, mem}; @@ -73,7 +73,7 @@ where /// Lock the mutex. /// /// This will wait for the mutex to be unlocked if it's already locked. - pub async fn lock(&self) -> MutexGuard<'_, M, T> { + pub fn lock(&self) -> impl Future> { poll_fn(|cx| { let ready = self.state.lock(|s| { let mut s = s.borrow_mut(); @@ -92,7 +92,6 @@ where Poll::Pending } }) - .await } /// Attempt to immediately lock the mutex. @@ -138,7 +137,7 @@ impl From for Mutex { impl Default for Mutex where M: RawMutex, - T: ?Sized + Default, + T: Default, { fn default() -> Self { Self::new(Default::default()) @@ -172,6 +171,7 @@ where /// /// Dropping it unlocks the mutex. #[clippy::has_significant_drop] +#[must_use = "if unused the Mutex will immediately unlock"] pub struct MutexGuard<'a, M, T> where M: RawMutex, diff --git a/embassy-sync/src/once_lock.rs b/embassy-sync/src/once_lock.rs index 55608ba32..cd05b986d 100644 --- a/embassy-sync/src/once_lock.rs +++ b/embassy-sync/src/once_lock.rs @@ -1,7 +1,7 @@ //! Synchronization primitive for initializing a value once, allowing others to await a reference to the value. use core::cell::Cell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::mem::MaybeUninit; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; @@ -55,7 +55,7 @@ impl OnceLock { /// Get a reference to the underlying value, waiting for it to be set. /// If the value is already set, this will return immediately. - pub async fn get(&self) -> &T { + pub fn get(&self) -> impl Future { poll_fn(|cx| match self.try_get() { Some(data) => Poll::Ready(data), None => { @@ -63,7 +63,6 @@ impl OnceLock { Poll::Pending } }) - .await } /// Try to get a reference to the underlying value if it exists. diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index cd5b8ed75..2598652d2 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -532,6 +532,250 @@ impl embedded_io_async::Write for Writer<'_, M, N> } } +// +// Type-erased variants +// + +pub(crate) trait DynamicPipe { + fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a>; + fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a>; + + fn try_read(&self, buf: &mut [u8]) -> Result; + fn try_write(&self, buf: &[u8]) -> Result; + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result; + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result; + + fn consume(&self, amt: usize); + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError>; +} + +impl DynamicPipe for Pipe +where + M: RawMutex, +{ + fn consume(&self, amt: usize) { + Pipe::consume(self, amt) + } + + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError> { + Pipe::try_fill_buf_with_context(self, cx) + } + + fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a> { + Pipe::write(self, buf).into() + } + + fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a> { + Pipe::read(self, buf).into() + } + + fn try_read(&self, buf: &mut [u8]) -> Result { + Pipe::try_read(self, buf) + } + + fn try_write(&self, buf: &[u8]) -> Result { + Pipe::try_write(self, buf) + } + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result { + Pipe::try_write_with_context(self, cx, buf) + } + + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result { + Pipe::try_read_with_context(self, cx, buf) + } +} + +/// Write-only access to a [`DynamicPipe`]. +pub struct DynamicWriter<'p> { + pipe: &'p dyn DynamicPipe, +} + +impl<'p> Clone for DynamicWriter<'p> { + fn clone(&self) -> Self { + *self + } +} + +impl<'p> Copy for DynamicWriter<'p> {} + +impl<'p> DynamicWriter<'p> { + /// Write some bytes to the pipe. + /// + /// See [`Pipe::write()`] + pub fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a> { + self.pipe.write(buf) + } + + /// Attempt to immediately write some bytes to the pipe. + /// + /// See [`Pipe::try_write()`] + pub fn try_write(&self, buf: &[u8]) -> Result { + self.pipe.try_write(buf) + } +} + +impl<'p, M, const N: usize> From> for DynamicWriter<'p> +where + M: RawMutex, +{ + fn from(value: Writer<'p, M, N>) -> Self { + Self { pipe: value.pipe } + } +} + +/// Future returned by [`DynamicWriter::write`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicWriteFuture<'p> { + pipe: &'p dyn DynamicPipe, + buf: &'p [u8], +} + +impl<'p> Future for DynamicWriteFuture<'p> { + type Output = usize; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_write_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryWriteError::Full) => Poll::Pending, + } + } +} + +impl<'p> Unpin for DynamicWriteFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicWriteFuture<'p> +where + M: RawMutex, +{ + fn from(value: WriteFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe, + buf: value.buf, + } + } +} + +/// Read-only access to a [`DynamicPipe`]. +pub struct DynamicReader<'p> { + pipe: &'p dyn DynamicPipe, +} + +impl<'p> DynamicReader<'p> { + /// Read some bytes from the pipe. + /// + /// See [`Pipe::read()`] + pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a> { + self.pipe.read(buf) + } + + /// Attempt to immediately read some bytes from the pipe. + /// + /// See [`Pipe::try_read()`] + pub fn try_read(&self, buf: &mut [u8]) -> Result { + self.pipe.try_read(buf) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn fill_buf(&mut self) -> DynamicFillBufFuture<'_> { + DynamicFillBufFuture { pipe: Some(self.pipe) } + } + + /// Try returning contents of the internal buffer. + /// + /// If no bytes are currently available to read, this function returns `Err(TryReadError::Empty)`. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn try_fill_buf(&mut self) -> Result<&[u8], TryReadError> { + unsafe { self.pipe.try_fill_buf_with_context(None) } + } + + /// 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.pipe.consume(amt) + } +} + +impl<'p, M, const N: usize> From> for DynamicReader<'p> +where + M: RawMutex, +{ + fn from(value: Reader<'p, M, N>) -> Self { + Self { pipe: value.pipe } + } +} + +/// Future returned by [`Pipe::read`] and [`Reader::read`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicReadFuture<'p> { + pipe: &'p dyn DynamicPipe, + buf: &'p mut [u8], +} + +impl<'p> Future for DynamicReadFuture<'p> { + type Output = usize; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_read_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryReadError::Empty) => Poll::Pending, + } + } +} + +impl<'p> Unpin for DynamicReadFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicReadFuture<'p> +where + M: RawMutex, +{ + fn from(value: ReadFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe, + buf: value.buf, + } + } +} + +/// Future returned by [`DynamicPipe::fill_buf`] and [`DynamicReader::fill_buf`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicFillBufFuture<'p> { + pipe: Option<&'p dyn DynamicPipe>, +} + +impl<'p> Future for DynamicFillBufFuture<'p> { + type Output = &'p [u8]; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pipe = self.pipe.take().unwrap(); + match unsafe { pipe.try_fill_buf_with_context(Some(cx)) } { + Ok(buf) => Poll::Ready(buf), + Err(TryReadError::Empty) => { + self.pipe = Some(pipe); + Poll::Pending + } + } + } +} + +impl<'p> Unpin for DynamicFillBufFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicFillBufFuture<'p> +where + M: RawMutex, +{ + fn from(value: FillBufFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe.map(|p| p as &dyn DynamicPipe), + } + } +} + #[cfg(test)] mod tests { use futures_executor::ThreadPool; @@ -619,6 +863,35 @@ mod tests { let _ = w.clone(); } + #[test] + fn dynamic_dispatch_pipe() { + let mut c = Pipe::::new(); + let (r, w) = c.split(); + let (mut r, w): (DynamicReader<'_>, DynamicWriter<'_>) = (r.into(), w.into()); + + assert!(w.try_write(&[42, 43]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[43]); + r.consume(1); + assert_eq!(r.try_fill_buf(), Err(TryReadError::Empty)); + assert_eq!(w.try_write(&[44, 45, 46]), Ok(1)); + assert_eq!(w.try_write(&[45, 46]), Ok(2)); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[44]); // only one byte due to wraparound. + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46]); + assert!(w.try_write(&[47]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46, 47]); + r.consume(3); + } + #[futures_test::test] async fn receiver_receives_given_try_write_async() { let executor = ThreadPool::new().unwrap(); diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index 24c6c5a7f..623c52993 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -71,6 +71,48 @@ where pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { self.channel.poll_ready_to_send(cx) } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicSender<'ch, T> @@ -133,6 +175,16 @@ where self.channel.try_receive() } + /// Peek at the next value without removing it from the queue. + /// + /// See [`PriorityChannel::try_peek()`] + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.channel.try_peek_with_context(None) + } + /// Allows a poll_fn to poll until the channel is ready to receive /// /// See [`PriorityChannel::poll_ready_to_receive()`] @@ -146,6 +198,59 @@ where pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.channel.poll_receive(cx) } + + /// Removes the elements from the channel that satisfy the predicate. + /// + /// See [`PriorityChannel::remove_if()`] + pub fn remove_if(&self, predicate: F) + where + F: Fn(&T) -> bool, + T: Clone, + { + self.channel.remove_if(predicate) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } } impl<'ch, M, T, K, const N: usize> From> for DynamicReceiver<'ch, T> @@ -248,6 +353,31 @@ where self.try_receive_with_context(None) } + fn try_peek(&mut self) -> Result + where + T: Clone, + { + self.try_peek_with_context(None) + } + + fn try_peek_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.peek() { + Ok(message.clone()) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { if self.queue.len() == self.queue.capacity() { self.senders_waker.wake(); @@ -316,6 +446,9 @@ where } fn clear(&mut self) { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } self.queue.clear(); } @@ -380,6 +513,13 @@ where self.lock(|c| c.try_receive_with_context(cx)) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek_with_context(cx)) + } + /// Poll the channel for the next message pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { self.lock(|c| c.poll_receive(cx)) @@ -450,6 +590,37 @@ where self.lock(|c| c.try_receive()) } + /// Peek at the next value without removing it from the queue. + /// + /// This method will either receive a copy of the message from the channel immediately or return + /// an error if the channel is empty. + pub fn try_peek(&self) -> Result + where + T: Clone, + { + self.lock(|c| c.try_peek()) + } + + /// Removes elements from the channel based on the given predicate. + pub fn remove_if(&self, predicate: F) + where + F: Fn(&T) -> bool, + T: Clone, + { + self.lock(|c| { + let mut new_heap = BinaryHeap::::new(); + for item in c.queue.iter() { + if !predicate(item) { + match new_heap.push(item.clone()) { + Ok(_) => (), + Err(_) => panic!("Error pushing item to heap"), + } + } + } + c.queue = new_heap; + }); + } + /// Returns the maximum number of elements the channel can hold. pub const fn capacity(&self) -> usize { N @@ -499,6 +670,13 @@ where PriorityChannel::try_receive_with_context(self, cx) } + fn try_peek_with_context(&self, cx: Option<&mut Context<'_>>) -> Result + where + T: Clone, + { + PriorityChannel::try_peek_with_context(self, cx) + } + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { PriorityChannel::poll_ready_to_send(self, cx) } @@ -587,6 +765,8 @@ mod tests { fn simple_send_and_receive() { let c = PriorityChannel::::new(); assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_peek().unwrap(), 1); + assert_eq!(c.try_peek().unwrap(), 1); assert_eq!(c.try_receive().unwrap(), 1); } @@ -607,6 +787,8 @@ mod tests { let r: DynamicReceiver<'_, u32> = c.receiver().into(); assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_peek().unwrap(), 1); + assert_eq!(r.try_peek().unwrap(), 1); assert_eq!(r.try_receive().unwrap(), 1); } diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index a97eb7d5b..606efff0a 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -27,8 +27,8 @@ pub use subscriber::{DynSubscriber, Subscriber}; /// /// - With [Pub::publish()] the publisher has to wait until there is space in the internal message queue. /// - With [Pub::publish_immediate()] the publisher doesn't await and instead lets the oldest message -/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive -/// an error to indicate that it has lagged. +/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive +/// an error to indicate that it has lagged. /// /// ## Example /// @@ -194,6 +194,25 @@ impl crate::pubsub::PubSubBehavior + for PubSubChannel +{ + fn publish_immediate(&self, message: T) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.publish_immediate(message) + }) + } + + fn capacity(&self) -> usize { + self.capacity() + } + + fn is_full(&self) -> bool { + self.is_full() + } +} + impl SealedPubSubBehavior for PubSubChannel { @@ -246,13 +265,6 @@ impl usize { - self.capacity() - } - fn free_capacity(&self) -> usize { self.free_capacity() } @@ -286,10 +294,6 @@ impl bool { self.is_empty() } - - fn is_full(&self) -> bool { - self.is_full() - } } /// Internal state for the PubSub channel @@ -417,6 +421,9 @@ impl PubSubSta } fn clear(&mut self) { + if self.is_full() { + self.publisher_wakers.wake(); + } self.queue.clear(); } @@ -445,8 +452,6 @@ pub enum Error { MaximumPublishersReached, } -/// 'Middle level' behaviour of the pubsub channel. -/// This trait is used so that Sub and Pub can be generic over the channel. trait SealedPubSubBehavior { /// Try to get a message from the queue with the given message id. /// @@ -462,12 +467,6 @@ trait SealedPubSubBehavior { /// If the queue is full and a context is given, then its waker is registered in the publisher wakers. fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T>; - /// Publish a message immediately - fn publish_immediate(&self, message: T); - - /// Returns the maximum number of elements the channel can hold. - fn capacity(&self) -> usize; - /// Returns the free capacity of the channel. /// /// This is equivalent to `capacity() - len()` @@ -482,9 +481,6 @@ trait SealedPubSubBehavior { /// Returns whether the channel is empty. fn is_empty(&self) -> bool; - /// Returns whether the channel is full. - fn is_full(&self) -> bool; - /// Let the channel know that a subscriber has dropped fn unregister_subscriber(&self, subscriber_next_message_id: u64); @@ -495,9 +491,16 @@ trait SealedPubSubBehavior { /// 'Middle level' behaviour of the pubsub channel. /// This trait is used so that Sub and Pub can be generic over the channel. #[allow(private_bounds)] -pub trait PubSubBehavior: SealedPubSubBehavior {} +pub trait PubSubBehavior: SealedPubSubBehavior { + /// Publish a message immediately + fn publish_immediate(&self, message: T); -impl> PubSubBehavior for C {} + /// Returns the maximum number of elements the channel can hold. + fn capacity(&self) -> usize; + + /// Returns whether the channel is full. + fn is_full(&self) -> bool; +} /// The result of the subscriber wait procedure #[derive(Debug, Clone, PartialEq, Eq)] @@ -755,4 +758,30 @@ mod tests { assert_eq!(1, sub0.try_next_message_pure().unwrap().0); assert_eq!(0, sub1.try_next_message_pure().unwrap().0); } + + #[futures_test::test] + async fn publisher_sink() { + use futures_util::{SinkExt, StreamExt}; + + let channel = PubSubChannel::::new(); + + let mut sub = channel.subscriber().unwrap(); + + let publ = channel.publisher().unwrap(); + let mut sink = publ.sink(); + + sink.send(0).await.unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + + sink.send(1).await.unwrap(); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + + sink.send_all(&mut futures_util::stream::iter(0..4).map(Ok)) + .await + .unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + assert_eq!(2, sub.try_next_message_pure().unwrap()); + assert_eq!(3, sub.try_next_message_pure().unwrap()); + } } diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index e66b3b1db..7a1ab66de 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -74,6 +74,12 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { pub fn is_full(&self) -> bool { self.channel.is_full() } + + /// Create a [`futures::Sink`] adapter for this publisher. + #[inline] + pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { + PubSink { publ: self, fut: None } + } } impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Pub<'a, PSB, T> { @@ -221,6 +227,67 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: } } +#[must_use = "Sinks do nothing unless polled"] +/// [`futures_sink::Sink`] adapter for [`Pub`]. +pub struct PubSink<'a, 'p, PSB, T> +where + T: Clone, + PSB: PubSubBehavior + ?Sized, +{ + publ: &'p Pub<'a, PSB, T>, + fut: Option>, +} + +impl<'a, 'p, PSB, T> PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + /// Try to make progress on the pending future if we have one. + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let Some(mut fut) = self.fut.take() else { + return Poll::Ready(()); + }; + + if Pin::new(&mut fut).poll(cx).is_pending() { + self.fut = Some(fut); + return Poll::Pending; + } + + Poll::Ready(()) + } +} + +impl<'a, 'p, PSB, T> futures_sink::Sink for PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + type Error = core::convert::Infallible; + + #[inline] + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.fut = Some(self.publ.publish(item)); + + Ok(()) + } + + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } +} + /// Future for the publisher wait action #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { diff --git a/embassy-sync/src/rwlock.rs b/embassy-sync/src/rwlock.rs new file mode 100644 index 000000000..deeadd167 --- /dev/null +++ b/embassy-sync/src/rwlock.rs @@ -0,0 +1,387 @@ +//! Async read-write lock. +//! +//! This module provides a read-write lock that can be used to synchronize data between asynchronous tasks. +use core::cell::{RefCell, UnsafeCell}; +use core::fmt; +use core::future::{poll_fn, Future}; +use core::ops::{Deref, DerefMut}; +use core::task::Poll; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex as BlockingMutex; +use crate::waitqueue::WakerRegistration; + +/// Error returned by [`RwLock::try_read`] and [`RwLock::try_write`] when the lock is already held. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TryLockError; + +struct State { + readers: usize, + writer: bool, + waker: WakerRegistration, +} + +/// Async read-write lock. +/// +/// The read-write lock is generic over the raw mutex implementation `M` and the data `T` it protects. +/// The raw read-write lock is used to guard access to the internal state. It +/// is held for very short periods only, while locking and unlocking. It is *not* held +/// for the entire time the async RwLock is locked. +/// +/// Which implementation you select depends on the context in which you're using the read-write lock. +/// +/// Use [`CriticalSectionRawMutex`](crate::blocking_mutex::raw::CriticalSectionRawMutex) when data can be shared between threads and interrupts. +/// +/// Use [`NoopRawMutex`](crate::blocking_mutex::raw::NoopRawMutex) when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeRawMutex`](crate::blocking_mutex::raw::ThreadModeRawMutex) when data is shared between tasks running on the same executor but you want a singleton. +/// + +pub struct RwLock +where + M: RawMutex, + T: ?Sized, +{ + state: BlockingMutex>, + inner: UnsafeCell, +} + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} + +/// Async read-write lock. +impl RwLock +where + M: RawMutex, +{ + /// Create a new read-write lock with the given value. + pub const fn new(value: T) -> Self { + Self { + inner: UnsafeCell::new(value), + state: BlockingMutex::new(RefCell::new(State { + readers: 0, + writer: false, + waker: WakerRegistration::new(), + })), + } + } +} + +impl RwLock +where + M: RawMutex, + T: ?Sized, +{ + /// Lock the read-write lock for reading. + /// + /// This will wait for the lock to be available if it's already locked for writing. + pub fn read(&self) -> impl Future> { + poll_fn(|cx| { + let ready = self.state.lock(|s| { + let mut s = s.borrow_mut(); + if s.writer { + s.waker.register(cx.waker()); + false + } else { + s.readers += 1; + true + } + }); + + if ready { + Poll::Ready(RwLockReadGuard { rwlock: self }) + } else { + Poll::Pending + } + }) + } + + /// Lock the read-write lock for writing. + /// + /// This will wait for the lock to be available if it's already locked for reading or writing. + pub fn write(&self) -> impl Future> { + poll_fn(|cx| { + let ready = self.state.lock(|s| { + let mut s = s.borrow_mut(); + if s.writer || s.readers > 0 { + s.waker.register(cx.waker()); + false + } else { + s.writer = true; + true + } + }); + + if ready { + Poll::Ready(RwLockWriteGuard { rwlock: self }) + } else { + Poll::Pending + } + }) + } + + /// Attempt to immediately lock the rwlock. + /// + /// If the rwlock is already locked, this will return an error instead of waiting. + pub fn try_read(&self) -> Result, TryLockError> { + self.state + .lock(|s| { + let mut s = s.borrow_mut(); + if s.writer { + return Err(()); + } + s.readers += 1; + Ok(()) + }) + .map_err(|_| TryLockError)?; + + Ok(RwLockReadGuard { rwlock: self }) + } + + /// Attempt to immediately lock the rwlock. + /// + /// If the rwlock is already locked, this will return an error instead of waiting. + pub fn try_write(&self) -> Result, TryLockError> { + self.state + .lock(|s| { + let mut s = s.borrow_mut(); + if s.writer || s.readers > 0 { + return Err(()); + } + s.writer = true; + Ok(()) + }) + .map_err(|_| TryLockError)?; + + Ok(RwLockWriteGuard { rwlock: self }) + } + + /// Consumes this read-write lock, returning the underlying data. + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the RwLock mutably, no actual locking needs to + /// take place -- the mutable borrow statically guarantees no locks exist. + pub fn get_mut(&mut self) -> &mut T { + self.inner.get_mut() + } +} + +impl From for RwLock { + fn from(from: T) -> Self { + Self::new(from) + } +} + +impl Default for RwLock +where + M: RawMutex, + T: Default, +{ + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for RwLock +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("RwLock"); + match self.try_read() { + Ok(guard) => d.field("inner", &&*guard), + Err(TryLockError) => d.field("inner", &"Locked"), + } + .finish_non_exhaustive() + } +} + +/// Async read lock guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the read-write lock for reading, and grants access to the contents. +/// +/// Dropping it unlocks the read-write lock. +#[clippy::has_significant_drop] +#[must_use = "if unused the RwLock will immediately unlock"] +pub struct RwLockReadGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized, +{ + rwlock: &'a RwLock, +} + +impl<'a, M, T> Drop for RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.rwlock.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.readers -= 1; + if s.readers == 0 { + s.waker.wake(); + } + }) + } +} + +impl<'a, M, T> Deref for RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the RwLockReadGuard represents shared access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &*(self.rwlock.inner.get() as *const T) } + } +} + +impl<'a, M, T> fmt::Debug for RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, M, T> fmt::Display for RwLockReadGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +/// Async write lock guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the read-write lock for writing, and grants access to the contents. +/// +/// Dropping it unlocks the read-write lock. +#[clippy::has_significant_drop] +#[must_use = "if unused the RwLock will immediately unlock"] +pub struct RwLockWriteGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized, +{ + rwlock: &'a RwLock, +} + +impl<'a, R, T> Drop for RwLockWriteGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.rwlock.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.writer = false; + s.waker.wake(); + }) + } +} + +impl<'a, R, T> Deref for RwLockWriteGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the RwLockWriteGuard represents exclusive access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &*(self.rwlock.inner.get() as *mut T) } + } +} + +impl<'a, R, T> DerefMut for RwLockWriteGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: the RwLockWriteGuard represents exclusive access to the contents + // of the read-write lock, so it's OK to get it. + unsafe { &mut *(self.rwlock.inner.get()) } + } +} + +impl<'a, R, T> fmt::Debug for RwLockWriteGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, R, T> fmt::Display for RwLockWriteGuard<'a, R, T> +where + R: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +#[cfg(test)] +mod tests { + use crate::blocking_mutex::raw::NoopRawMutex; + use crate::rwlock::RwLock; + + #[futures_test::test] + async fn read_guard_releases_lock_when_dropped() { + let rwlock: RwLock = RwLock::new([0, 1]); + + { + let guard = rwlock.read().await; + assert_eq!(*guard, [0, 1]); + } + + { + let guard = rwlock.read().await; + assert_eq!(*guard, [0, 1]); + } + + assert_eq!(*rwlock.read().await, [0, 1]); + } + + #[futures_test::test] + async fn write_guard_releases_lock_when_dropped() { + let rwlock: RwLock = RwLock::new([0, 1]); + + { + let mut guard = rwlock.write().await; + assert_eq!(*guard, [0, 1]); + guard[1] = 2; + } + + { + let guard = rwlock.read().await; + assert_eq!(*guard, [0, 2]); + } + + assert_eq!(*rwlock.read().await, [0, 2]); + } +} diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index a0f4b5a74..e7095401e 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -6,7 +6,7 @@ use core::task::{Context, Poll, Waker}; use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; -/// Single-slot signaling primitive. +/// Single-slot signaling primitive for a _single_ consumer. /// /// This is similar to a [`Channel`](crate::channel::Channel) with a buffer size of 1, except /// "sending" to it (calling [`Signal::signal`]) when full will overwrite the previous value instead @@ -17,6 +17,7 @@ use crate::blocking_mutex::Mutex; /// updates. /// /// For more advanced use cases, you might want to use [`Channel`](crate::channel::Channel) instead. +/// For multiple consumers, use [`Watch`](crate::watch::Watch) instead. /// /// Signals are generally declared as `static`s and then borrowed as required. /// @@ -106,7 +107,7 @@ where }) } - /// Future that completes when this Signal has been signaled. + /// Future that completes when this Signal has been signaled, taking the value out of the signal. pub fn wait(&self) -> impl Future + '_ { poll_fn(move |cx| self.poll_wait(cx)) } diff --git a/embassy-sync/src/waitqueue/atomic_waker.rs b/embassy-sync/src/waitqueue/atomic_waker.rs index 63fe04a6e..5a9910e7f 100644 --- a/embassy-sync/src/waitqueue/atomic_waker.rs +++ b/embassy-sync/src/waitqueue/atomic_waker.rs @@ -1,26 +1,28 @@ use core::cell::Cell; use core::task::Waker; -use crate::blocking_mutex::raw::CriticalSectionRawMutex; +use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; use crate::blocking_mutex::Mutex; /// Utility struct to register and wake a waker. -pub struct AtomicWaker { - waker: Mutex>>, +/// If a waker is registered, registering another waker will replace the previous one without waking it. +/// Intended to wake a task from an interrupt. Therefore, it is generally not expected, +/// that multiple tasks register try to register a waker simultaneously. +pub struct GenericAtomicWaker { + waker: Mutex>>, } -impl AtomicWaker { +impl GenericAtomicWaker { /// Create a new `AtomicWaker`. - pub const fn new() -> Self { + pub const fn new(mutex: M) -> Self { Self { - waker: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), + waker: Mutex::const_new(mutex, Cell::new(None)), } } /// Register a waker. Overwrites the previous waker, if any. pub fn register(&self, w: &Waker) { - critical_section::with(|cs| { - let cell = self.waker.borrow(cs); + self.waker.lock(|cell| { cell.set(match cell.replace(None) { Some(w2) if (w2.will_wake(w)) => Some(w2), _ => Some(w.clone()), @@ -30,8 +32,7 @@ impl AtomicWaker { /// Wake the registered waker, if any. pub fn wake(&self) { - critical_section::with(|cs| { - let cell = self.waker.borrow(cs); + self.waker.lock(|cell| { if let Some(w) = cell.replace(None) { w.wake_by_ref(); cell.set(Some(w)); @@ -39,3 +40,27 @@ impl AtomicWaker { }) } } + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: GenericAtomicWaker, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: GenericAtomicWaker::new(CriticalSectionRawMutex::new()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.register(w); + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + self.waker.wake(); + } +} diff --git a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs index 5c6a96ec8..c06b83056 100644 --- a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs +++ b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs @@ -4,6 +4,9 @@ use core::sync::atomic::{AtomicPtr, Ordering}; use core::task::Waker; /// Utility struct to register and wake a waker. +/// If a waker is registered, registering another waker will replace the previous one without waking it. +/// The intended use case is to wake tasks from interrupts. Therefore, it is generally not expected, +/// that multiple tasks register try to register a waker simultaneously. pub struct AtomicWaker { waker: AtomicPtr<()>, } diff --git a/embassy-sync/src/waitqueue/multi_waker.rs b/embassy-sync/src/waitqueue/multi_waker.rs index 0e520bf40..0384d6bed 100644 --- a/embassy-sync/src/waitqueue/multi_waker.rs +++ b/embassy-sync/src/waitqueue/multi_waker.rs @@ -3,6 +3,8 @@ use core::task::Waker; use heapless::Vec; /// Utility struct to register and wake multiple wakers. +/// Queue of wakers with a maximum length of `N`. +/// Intended for waking multiple tasks. pub struct MultiWakerRegistration { wakers: Vec, } diff --git a/embassy-sync/src/waitqueue/waker_registration.rs b/embassy-sync/src/waitqueue/waker_registration.rs index 9b666e7c4..7f24f8fb6 100644 --- a/embassy-sync/src/waitqueue/waker_registration.rs +++ b/embassy-sync/src/waitqueue/waker_registration.rs @@ -2,6 +2,10 @@ use core::mem; use core::task::Waker; /// Utility struct to register and wake a waker. +/// If a waker is registered, registering another waker will replace the previous one. +/// The previous waker will be woken in this case, giving it a chance to reregister itself. +/// Although it is possible to wake multiple tasks this way, +/// this will cause them to wake each other in a loop registering themselves. #[derive(Debug, Default)] pub struct WakerRegistration { waker: Option, diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs new file mode 100644 index 000000000..08d6a833d --- /dev/null +++ b/embassy-sync/src/watch.rs @@ -0,0 +1,1121 @@ +//! A synchronization primitive for passing the latest value to **multiple** receivers. + +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +/// The `Watch` is a single-slot signaling primitive that allows _multiple_ (`N`) receivers to concurrently await +/// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, +/// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous +/// value when a new one is sent, without waiting for all receivers to read the previous value. +/// +/// This makes `Watch` particularly useful when a single task updates a value or "state", and multiple other tasks +/// need to be notified about changes to this value asynchronously. Receivers may "lose" stale values, as they are +/// always provided with the latest value. +/// +/// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained where relevant. An [`AnonReceiver`] +/// and [`DynAnonReceiver`] are also available, which do not increase the receiver count for the +/// channel, and unwrapping is therefore not required, but it is not possible to `.await` the channel. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::watch::Watch; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static WATCH: Watch = Watch::new(); +/// +/// // Obtain receivers and sender +/// let mut rcv0 = WATCH.receiver().unwrap(); +/// let mut rcv1 = WATCH.dyn_receiver().unwrap(); +/// let mut snd = WATCH.sender(); +/// +/// // No more receivers, and no update +/// assert!(WATCH.receiver().is_none()); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(10); +/// +/// // Receive the new value (async or try) +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(20); +/// +/// // Using `get` marks the value as seen +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// // But `get` also returns when unchanged +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// +/// }; +/// block_on(f); +/// ``` +pub struct Watch { + mutex: Mutex>>, +} + +struct WatchState { + data: Option, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +trait SealedWatchBehavior { + /// Poll the `Watch` for the current value, making it as seen. + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, making it as seen. + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. + fn try_changed(&self, id: &mut u64) -> Option; + + /// Poll the `Watch` for a changed value that matches the predicate function + /// `f`, marking it as seen. + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed and matches the + /// predicate function `f`, marking it as seen. + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); + + /// Clears the value of the `Watch`. + fn clear(&self); + + /// Sends a new value to the `Watch`. + fn send(&self, val: T); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_modify(&self, f: &mut dyn Fn(&mut Option)); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); +} + +/// A trait representing the 'inner' behavior of the `Watch`. +#[allow(private_bounds)] +pub trait WatchBehavior: SealedWatchBehavior { + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; + + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn contains_value(&self) -> bool; +} + +impl SealedWatchBehavior for Watch { + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > *id { + true => { + *id = s.current_id; + s.data.clone() + } + false => None, + } + }) + } + + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + s.data.clone() + } + _ => None, + } + }) + } + + fn drop_receiver(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.receiver_count -= 1; + }) + } + + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + f(&mut s.data); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if f(&mut s.data) { + s.current_id += 1; + s.wakers.wake(); + } + }) + } +} + +impl WatchBehavior for Watch { + fn try_get(&self, id: Option<&mut u64>) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() + }) + } + + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + if let Some(id) = id { + *id = s.current_id; + } + Some(data.clone()) + } + _ => None, + } + }) + } + + fn contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + +impl Watch { + /// Create a new `Watch` channel for `N` receivers. + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new `Watch` channel with default data. + pub const fn new_with(data: T) -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: Some(data), + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new [`Sender`] for the `Watch`. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender(Snd::new(self)) + } + + /// Create a new [`DynSender`] for the `Watch`. + pub fn dyn_sender(&self) -> DynSender<'_, T> { + DynSender(Snd::new(self)) + } + + /// Try to create a new [`Receiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(Receiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`DynReceiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn dyn_receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(DynReceiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`AnonReceiver`] for the `Watch`. + pub fn anon_receiver(&self) -> AnonReceiver<'_, M, T, N> { + AnonReceiver(AnonRcv::new(self, 0)) + } + + /// Try to create a new [`DynAnonReceiver`] for the `Watch`. + pub fn dyn_anon_receiver(&self) -> DynAnonReceiver<'_, T> { + DynAnonReceiver(AnonRcv::new(self, 0)) + } + + /// Returns the message ID of the latest message sent to the `Watch`. + /// + /// This counter is monotonic, and is incremented every time a new message is sent. + pub fn get_msg_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Tries to get the value of the `Watch`. + pub fn try_get(&self) -> Option { + WatchBehavior::try_get(self, None) + } + + /// Tries to get the value of the `Watch` if it matches the predicate function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + WatchBehavior::try_get_and(self, None, &mut f) + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Clone for Snd<'a, T, W> { + fn clone(&self) -> Self { + Self { + watch: self.watch, + _phantom: PhantomData, + } + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + _phantom: PhantomData, + } + } + + /// Sends a new value to the `Watch`. + pub fn send(&self, val: T) { + self.watch.send(val) + } + + /// Clears the value of the `Watch`. + /// This will cause calls to [`Rcv::get`] to be pending. + pub fn clear(&self) { + self.watch.clear() + } + + /// Tries to retrieve the value of the `Watch`. + pub fn try_get(&self) -> Option { + self.watch.try_get(None) + } + + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(None, &mut f) + } + + /// Returns true if the `Watch` contains a value. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } + + /// Modify the value of the `Watch` using a closure. + pub fn send_modify(&self, mut f: F) + where + F: Fn(&mut Option), + { + self.watch.send_modify(&mut f) + } + + /// Modify the value of the `Watch` using a closure. The closure must return + /// `true` if the value was modified, which notifies all receivers. + pub fn send_if_modified(&self, mut f: F) + where + F: Fn(&mut Option) -> bool, + { + self.watch.send_if_modified(&mut f) + } +} + +/// A sender of a `Watch` channel. +/// +/// For a simpler type definition, consider [`DynSender`] at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct Sender<'a, M: RawMutex, T: Clone, const N: usize>(Snd<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Clone for Sender<'a, M, T, N> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Sender<'a, M, T, N> { + /// Converts the `Sender` into a [`DynSender`]. + pub fn as_dyn(self) -> DynSender<'a, T> { + DynSender(Snd::new(self.watch)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Sender<'a, M, T, N> { + fn into(self) -> DynSender<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Sender<'a, M, T, N> { + type Target = Snd<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Sender<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A sender which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Sender`] with a simpler type definition, +pub struct DynSender<'a, T: Clone>(Snd<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Clone for DynSender<'a, T> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, T: Clone> Deref for DynSender<'a, T> { + type Target = Snd<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSender<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub fn get(&mut self) -> impl Future + '_ { + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)) + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Returns the value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_get_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Waits for the `Watch` to change and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed(&mut self) -> T { + poll_fn(|cx| self.watch.poll_changed(&mut self.at_id, cx)).await + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Waits for the `Watch` to change to a value which satisfies the predicate + /// function `f` and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_changed_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { + fn drop(&mut self) { + self.watch.drop_receiver(); + } +} + +/// A anonymous receiver can NOT `.await` a change in the `Watch` value. +pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> AnonRcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +/// A receiver of a `Watch` channel. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynReceiver<'a, T> { + let rcv = DynReceiver(Rcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Receiver<'a, M, T, N> { + fn into(self) -> DynReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynReceiver<'a, T> { + type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver of a `Watch` channel that cannot `.await` values. +pub struct AnonReceiver<'a, M: RawMutex, T: Clone, const N: usize>(AnonRcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> AnonReceiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynAnonReceiver<'a, T> { + let rcv = DynAnonReceiver(AnonRcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for AnonReceiver<'a, M, T, N> { + fn into(self) -> DynAnonReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for AnonReceiver<'a, M, T, N> { + type Target = AnonRcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for AnonReceiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver that cannot `.await` value, which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`AnonReceiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynAnonReceiver<'a, T: Clone>(AnonRcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynAnonReceiver<'a, T> { + type Target = AnonRcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynAnonReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + use futures_executor::block_on; + + use super::Watch; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + + #[test] + fn multiple_sends() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.changed().await, 10); + + // Receive another value + snd.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + + // No update + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn all_try_get() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(WATCH.try_get(), None); + assert_eq!(rcv.try_get(), None); + assert_eq!(snd.try_get(), None); + + // Receive the new value + snd.send(10); + assert_eq!(WATCH.try_get(), Some(10)); + assert_eq!(rcv.try_get(), Some(10)); + assert_eq!(snd.try_get(), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(10)); + assert_eq!(snd.try_get_and(|x| x > &5), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x < &5), None); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert_eq!(snd.try_get_and(|x| x < &5), None); + }; + block_on(f); + } + + #[test] + fn once_lock_like() { + let f = async { + static CONFIG0: u8 = 10; + static CONFIG1: u8 = 20; + + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(&CONFIG0); + let rcv0 = rcv.changed().await; + assert_eq!(rcv0, &10); + + // Receive another value + snd.send(&CONFIG1); + let rcv1 = rcv.try_changed(); + assert_eq!(rcv1, Some(&20)); + + // No update + assert_eq!(rcv.try_changed(), None); + + // Ensure similarity with original static + assert_eq!(rcv0, &CONFIG0); + assert_eq!(rcv1, Some(&CONFIG1)); + }; + block_on(f); + } + + #[test] + fn sender_modify() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Modify the value inplace + snd.send_modify(|opt| { + if let Some(inner) = opt { + *inner += 5; + } + }); + + // Get the modified value + assert_eq!(rcv.try_changed(), Some(15)); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn predicate_fn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + snd.send(15); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert!(rcv.try_changed().is_none()); + + snd.send(20); + assert_eq!(rcv.try_changed_and(|x| x > &5), Some(20)); + assert_eq!(rcv.try_changed_and(|x| x > &5), None); + + snd.send(25); + assert_eq!(rcv.try_changed_and(|x| x < &5), None); + assert_eq!(rcv.try_changed(), Some(25)); + + snd.send(30); + assert_eq!(rcv.changed_and(|x| x > &5).await, 30); + assert_eq!(rcv.get_and(|x| x > &5).await, 30); + }; + block_on(f); + } + + #[test] + fn receive_after_create() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain sender and send value + let snd = WATCH.sender(); + snd.send(10); + + // Obtain receiver and receive value + let mut rcv = WATCH.receiver().unwrap(); + assert_eq!(rcv.try_changed(), Some(10)); + }; + block_on(f); + } + + #[test] + fn max_receivers_drop() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Try to create 3 receivers (only 2 can exist at once) + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Drop the first receiver + drop(rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + assert!(rcv3.is_some()); + }; + block_on(f); + } + + #[test] + fn multiple_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receivers and sender + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.anon_receiver(); + let snd = WATCH.sender(); + + // No update for both + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + // Send a new value + snd.send(0); + + // Both receivers receive the new value + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + }; + block_on(f); + } + + #[test] + fn clone_senders() { + let f = async { + // Obtain different ways to send + static WATCH: Watch = Watch::new(); + let snd0 = WATCH.sender(); + let snd1 = snd0.clone(); + + // Obtain Receiver + let mut rcv = WATCH.receiver().unwrap().as_dyn(); + + // Send a value from first sender + snd0.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Send a value from second sender + snd1.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + }; + block_on(f); + } + + #[test] + fn use_dynamics() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut anon_rcv = WATCH.dyn_anon_receiver(); + let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); + let dyn_snd = WATCH.dyn_sender(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn convert_to_dyn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let anon_rcv = WATCH.anon_receiver(); + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Convert to dynamic + let mut dyn_anon_rcv = anon_rcv.as_dyn(); + let mut dyn_rcv = rcv.as_dyn(); + let dyn_snd = snd.as_dyn(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn dynamic_receiver_count() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Convert to dynamic + let dyn_rcv0 = rcv0.unwrap().as_dyn(); + + // Drop the (now dynamic) receiver + drop(dyn_rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + let rcv4 = WATCH.receiver(); + assert!(rcv3.is_some()); + assert!(rcv4.is_none()); + }; + block_on(f); + } + + #[test] + fn contains_value() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), false); + assert_eq!(snd.contains_value(), false); + + // Send a value + snd.send(10); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), true); + assert_eq!(snd.contains_value(), true); + }; + block_on(f); + } +} diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index cfce9a571..e3e5b2538 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -15,7 +15,7 @@ //! another message will result in an error being returned. use core::cell::RefCell; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::marker::PhantomData; use core::task::{Context, Poll}; @@ -35,7 +35,7 @@ use crate::waitqueue::WakerRegistration; /// The channel requires a buffer of recyclable elements. Writing to the channel is done through /// an `&mut T`. pub struct Channel<'a, M: RawMutex, T> { - buf: *mut T, + buf: BufferPtr, phantom: PhantomData<&'a mut T>, state: Mutex>, } @@ -50,10 +50,10 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { assert!(len != 0); Self { - buf: buf.as_mut_ptr(), + buf: BufferPtr(buf.as_mut_ptr()), phantom: PhantomData, state: Mutex::new(RefCell::new(State { - len, + capacity: len, front: 0, back: 0, full: false, @@ -70,8 +70,42 @@ impl<'a, M: RawMutex, T> Channel<'a, M, T> { pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { (Sender { channel: self }, Receiver { channel: self }) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.state.lock(|s| s.borrow().is_full()) + } } +#[repr(transparent)] +struct BufferPtr(*mut T); + +impl BufferPtr { + unsafe fn add(&self, count: usize) -> *mut T { + self.0.add(count) + } +} + +unsafe impl Send for BufferPtr {} +unsafe impl Sync for BufferPtr {} + /// Send-only access to a [`Channel`]. pub struct Sender<'a, M: RawMutex, T> { channel: &'a Channel<'a, M, T>, @@ -109,12 +143,15 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { } /// Asynchronously send a value over the channel. - pub async fn send(&mut self) -> &mut T { - let i = poll_fn(|cx| { + pub fn send(&mut self) -> impl Future { + poll_fn(|cx| { self.channel.state.lock(|s| { let s = &mut *s.borrow_mut(); match s.push_index() { - Some(i) => Poll::Ready(i), + Some(i) => { + let r = unsafe { &mut *self.channel.buf.add(i) }; + Poll::Ready(r) + } None => { s.receive_waker.register(cx.waker()); Poll::Pending @@ -122,14 +159,34 @@ impl<'a, M: RawMutex, T> Sender<'a, M, T> { } }) }) - .await; - unsafe { &mut *self.channel.buf.add(i) } } /// Notify the channel that the sending of the value has been finalized. pub fn send_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().push_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } /// Receive-only access to a [`Channel`]. @@ -138,7 +195,7 @@ pub struct Receiver<'a, M: RawMutex, T> { } impl<'a, M: RawMutex, T> Receiver<'a, M, T> { - /// Creates one further [`Sender`] over the same channel. + /// Creates one further [`Receiver`] over the same channel. pub fn borrow(&mut self) -> Receiver<'_, M, T> { Receiver { channel: self.channel } } @@ -169,12 +226,15 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { } /// Asynchronously receive a value over the channel. - pub async fn receive(&mut self) -> &mut T { - let i = poll_fn(|cx| { + pub fn receive(&mut self) -> impl Future { + poll_fn(|cx| { self.channel.state.lock(|s| { let s = &mut *s.borrow_mut(); match s.pop_index() { - Some(i) => Poll::Ready(i), + Some(i) => { + let r = unsafe { &mut *self.channel.buf.add(i) }; + Poll::Ready(r) + } None => { s.send_waker.register(cx.waker()); Poll::Pending @@ -182,18 +242,39 @@ impl<'a, M: RawMutex, T> Receiver<'a, M, T> { } }) }) - .await; - unsafe { &mut *self.channel.buf.add(i) } } /// Notify the channel that the receiving of the value has been finalized. pub fn receive_done(&mut self) { self.channel.state.lock(|s| s.borrow_mut().pop_done()) } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } } struct State { - len: usize, + /// Maximum number of elements the channel can hold. + capacity: usize, /// Front index. Always 0..=(N-1) front: usize, @@ -210,13 +291,34 @@ struct State { impl State { fn increment(&self, i: usize) -> usize { - if i + 1 == self.len { + if i + 1 == self.capacity { 0 } else { i + 1 } } + fn clear(&mut self) { + if self.full { + self.receive_waker.wake(); + } + self.front = 0; + self.back = 0; + self.full = false; + } + + fn len(&self) -> usize { + if !self.full { + if self.back >= self.front { + self.back - self.front + } else { + self.capacity + self.back - self.front + } + } else { + self.capacity + } + } + fn is_full(&self) -> bool { self.full } diff --git a/embassy-time-driver/CHANGELOG.md b/embassy-time-driver/CHANGELOG.md new file mode 100644 index 000000000..744b0f648 --- /dev/null +++ b/embassy-time-driver/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog for embassy-time-driver + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.2.0 - 2025-01-02 + +- The `allocate_alarm`, `set_alarm_callback`, `set_alarm` functions have been removed. +- `schedule_wake` has been added to the `Driver` trait. + +## 0.1.0 - 2024-01-11 + +Initial release diff --git a/embassy-time-driver/Cargo.toml b/embassy-time-driver/Cargo.toml index d9f2e97df..16213cb75 100644 --- a/embassy-time-driver/Cargo.toml +++ b/embassy-time-driver/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-time-driver" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = "Driver trait for embassy-time" repository = "https://github.com/embassy-rs/embassy" @@ -226,6 +226,8 @@ tick-hz-128_000_000 = [] tick-hz-130_000_000 = [] ## 131.072MHz Tick Rate tick-hz-131_072_000 = [] +## 133.0MHz Tick Rate +tick-hz-133_000_000 = [] ## 140.0MHz Tick Rate tick-hz-140_000_000 = [] ## 144.0MHz Tick Rate @@ -368,3 +370,7 @@ tick-hz-5_242_880_000 = [] [dependencies] document-features = "0.2.7" + +[dev-dependencies] +critical-section = "1" +embassy-time-queue-utils = { path = "../embassy-time-queue-utils" } \ No newline at end of file diff --git a/embassy-time-driver/gen_tick.py b/embassy-time-driver/gen_tick.py index af194c31f..080434457 100644 --- a/embassy-time-driver/gen_tick.py +++ b/embassy-time-driver/gen_tick.py @@ -22,6 +22,7 @@ for i in range(1, 30): ticks.append(10 * i * 1_000_000) for i in range(15, 50): ticks.append(20 * i * 1_000_000) +ticks.append(133 * 1_000_000) seen = set() ticks = sorted([x for x in ticks if not (x in seen or seen.add(x))]) diff --git a/embassy-time-driver/src/lib.rs b/embassy-time-driver/src/lib.rs index 565597935..32cb68296 100644 --- a/embassy-time-driver/src/lib.rs +++ b/embassy-time-driver/src/lib.rs @@ -17,12 +17,79 @@ //! Otherwise, don’t enable any `tick-hz-*` feature to let the user configure the tick rate themselves by //! enabling a feature on `embassy-time`. //! +//! ### Example +//! +//! ``` +//! use core::task::Waker; +//! +//! use embassy_time_driver::Driver; +//! +//! struct MyDriver{} // not public! +//! +//! impl Driver for MyDriver { +//! fn now(&self) -> u64 { +//! todo!() +//! } +//! +//! fn schedule_wake(&self, at: u64, waker: &Waker) { +//! todo!() +//! } +//! } +//! +//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{}); +//! ``` +//! +//! ## Implementing the timer queue +//! +//! The simplest (but suboptimal) way to implement a timer queue is to define a single queue in the +//! time driver. Declare a field protected by an appropriate mutex (e.g. `critical_section::Mutex`). +//! +//! Then, you'll need to adapt the `schedule_wake` method to use this queue. +//! +//! Note that if you are using multiple queues, you will need to ensure that a single timer +//! queue item is only ever enqueued into a single queue at a time. +//! +//! ``` +//! use core::cell::RefCell; +//! use core::task::Waker; +//! +//! use critical_section::{CriticalSection, Mutex}; +//! use embassy_time_queue_utils::Queue; +//! use embassy_time_driver::Driver; +//! +//! struct MyDriver { +//! queue: Mutex>, +//! } +//! +//! impl MyDriver { +//! fn set_alarm(&self, cs: &CriticalSection, at: u64) -> bool { +//! todo!() +//! } +//! } +//! +//! impl Driver for MyDriver { +//! fn now(&self) -> u64 { todo!() } +//! +//! fn schedule_wake(&self, at: u64, waker: &Waker) { +//! critical_section::with(|cs| { +//! let mut queue = self.queue.borrow(cs).borrow_mut(); +//! if queue.schedule_wake(at, waker) { +//! let mut next = queue.next_expiration(self.now()); +//! while !self.set_alarm(&cs, next) { +//! next = queue.next_expiration(self.now()); +//! } +//! } +//! }); +//! } +//! } +//! ``` +//! //! # Linkage details //! //! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions. //! -//! `embassy` internally defines the driver functions as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls them. -//! The driver crate defines the functions as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the +//! `embassy` internally defines the driver function as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls it. +//! The driver crate defines the function as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the //! calls from the `embassy` crate to call into the driver crate. //! //! If there is none or multiple drivers in the crate tree, linking will fail. @@ -34,35 +101,12 @@ //! - It means comparing `Instant`s will always make sense: if there were multiple drivers //! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which //! would yield incorrect results. -//! -//! # Example -//! -//! ``` -//! use embassy_time_driver::{Driver, AlarmHandle}; -//! -//! struct MyDriver{} // not public! -//! -//! impl Driver for MyDriver { -//! fn now(&self) -> u64 { -//! todo!() -//! } -//! unsafe fn allocate_alarm(&self) -> Option { -//! todo!() -//! } -//! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { -//! todo!() -//! } -//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { -//! todo!() -//! } -//! } -//! -//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{}); -//! ``` //! ## Feature flags #![doc = document_features::document_features!(feature_label = r#"{feature}"#)] +use core::task::Waker; + mod tick; /// Ticks per second of the global timebase. @@ -70,35 +114,13 @@ mod tick; /// This value is specified by the [`tick-*` Cargo features](crate#tick-rate) pub const TICK_HZ: u64 = tick::TICK_HZ; -/// Alarm handle, assigned by the driver. -#[derive(Clone, Copy)] -pub struct AlarmHandle { - id: u8, -} - -impl AlarmHandle { - /// Create an AlarmHandle - /// - /// Safety: May only be called by the current global Driver impl. - /// The impl is allowed to rely on the fact that all `AlarmHandle` instances - /// are created by itself in unsafe code (e.g. indexing operations) - pub unsafe fn new(id: u8) -> Self { - Self { id } - } - - /// Get the ID of the AlarmHandle. - pub fn id(&self) -> u8 { - self.id - } -} - /// Time driver pub trait Driver: Send + Sync + 'static { /// Return the current timestamp in ticks. /// /// Implementations MUST ensure that: /// - This is guaranteed to be monotonic, i.e. a call to now() will always return - /// a greater or equal value than earler calls. Time can't "roll backwards". + /// a greater or equal value than earlier calls. Time can't "roll backwards". /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say /// in 10_000 years (Human civilization is likely to already have self-destructed /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers @@ -106,61 +128,26 @@ pub trait Driver: Send + Sync + 'static { /// or chaining multiple timers together. fn now(&self) -> u64; - /// Try allocating an alarm handle. Returns None if no alarms left. - /// Initially the alarm has no callback set, and a null `ctx` pointer. - /// - /// # Safety - /// It is UB to make the alarm fire before setting a callback. - unsafe fn allocate_alarm(&self) -> Option; - - /// Sets the callback function to be called when the alarm triggers. - /// The callback may be called from any context (interrupt or thread mode). - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - - /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm - /// timestamp, the provided callback function will be called. - /// - /// The `Driver` implementation should guarantee that the alarm callback is never called synchronously from `set_alarm`. - /// Rather - if `timestamp` is already in the past - `false` should be returned and alarm should not be set, - /// or alternatively, the driver should return `true` and arrange to call the alarm callback as soon as possible, but not synchronously. - /// There is a rare third possibility that the alarm was barely in the future, and by the time it was enabled, it had slipped into the - /// past. This is can be detected by double-checking that the alarm is still in the future after enabling it; if it isn't, `false` - /// should also be returned to indicate that the callback may have been called already by the alarm, but it is not guaranteed, so the - /// caller should also call the callback, just like in the more common `false` case. (Note: This requires idempotency of the callback.) - /// - /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. - /// - /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any. - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool; + /// Schedules a waker to be awoken at moment `at`. + /// If this moment is in the past, the waker might be awoken immediately. + fn schedule_wake(&self, at: u64, waker: &Waker); } extern "Rust" { fn _embassy_time_now() -> u64; - fn _embassy_time_allocate_alarm() -> Option; - fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool; + fn _embassy_time_schedule_wake(at: u64, waker: &Waker); } /// See [`Driver::now`] +#[inline] pub fn now() -> u64 { unsafe { _embassy_time_now() } } -/// See [`Driver::allocate_alarm`] -/// -/// Safety: it is UB to make the alarm fire before setting a callback. -pub unsafe fn allocate_alarm() -> Option { - _embassy_time_allocate_alarm() -} - -/// See [`Driver::set_alarm_callback`] -pub fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - unsafe { _embassy_time_set_alarm_callback(alarm, callback, ctx) } -} - -/// See [`Driver::set_alarm`] -pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool { - unsafe { _embassy_time_set_alarm(alarm, timestamp) } +/// Schedule the given waker to be woken at `at`. +#[inline] +pub fn schedule_wake(at: u64, waker: &Waker) { + unsafe { _embassy_time_schedule_wake(at, waker) } } /// Set the time Driver implementation. @@ -172,23 +159,15 @@ macro_rules! time_driver_impl { static $name: $t = $val; #[no_mangle] + #[inline] fn _embassy_time_now() -> u64 { <$t as $crate::Driver>::now(&$name) } #[no_mangle] - unsafe fn _embassy_time_allocate_alarm() -> Option<$crate::AlarmHandle> { - <$t as $crate::Driver>::allocate_alarm(&$name) - } - - #[no_mangle] - fn _embassy_time_set_alarm_callback(alarm: $crate::AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - <$t as $crate::Driver>::set_alarm_callback(&$name, alarm, callback, ctx) - } - - #[no_mangle] - fn _embassy_time_set_alarm(alarm: $crate::AlarmHandle, timestamp: u64) -> bool { - <$t as $crate::Driver>::set_alarm(&$name, alarm, timestamp) + #[inline] + fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) { + <$t as $crate::Driver>::schedule_wake(&$name, at, waker); } }; } diff --git a/embassy-time-driver/src/tick.rs b/embassy-time-driver/src/tick.rs index 916ae9498..5059e1628 100644 --- a/embassy-time-driver/src/tick.rs +++ b/embassy-time-driver/src/tick.rs @@ -182,6 +182,8 @@ pub const TICK_HZ: u64 = 128_000_000; pub const TICK_HZ: u64 = 130_000_000; #[cfg(feature = "tick-hz-131_072_000")] pub const TICK_HZ: u64 = 131_072_000; +#[cfg(feature = "tick-hz-133_000_000")] +pub const TICK_HZ: u64 = 133_000_000; #[cfg(feature = "tick-hz-140_000_000")] pub const TICK_HZ: u64 = 140_000_000; #[cfg(feature = "tick-hz-144_000_000")] @@ -410,6 +412,7 @@ pub const TICK_HZ: u64 = 5_242_880_000; feature = "tick-hz-128_000_000", feature = "tick-hz-130_000_000", feature = "tick-hz-131_072_000", + feature = "tick-hz-133_000_000", feature = "tick-hz-140_000_000", feature = "tick-hz-144_000_000", feature = "tick-hz-150_000_000", diff --git a/embassy-time-queue-driver/Cargo.toml b/embassy-time-queue-driver/Cargo.toml deleted file mode 100644 index 9ce9d79bb..000000000 --- a/embassy-time-queue-driver/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "embassy-time-queue-driver" -version = "0.1.0" -edition = "2021" -description = "Timer queue driver trait for embassy-time" -repository = "https://github.com/embassy-rs/embassy" -documentation = "https://docs.embassy.dev/embassy-time-queue-driver" -readme = "README.md" -license = "MIT OR Apache-2.0" -categories = [ - "embedded", - "no-std", - "concurrency", - "asynchronous", -] - -# Prevent multiple copies of this crate in the same binary. -# Needed because different copies might get different tick rates, causing -# wrong delays if the time driver is using one copy and user code is using another. -# This is especially common when mixing crates from crates.io and git. -links = "embassy-time-queue" - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-driver-v$VERSION/embassy-time-queue-driver/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-driver/src/" -target = "x86_64-unknown-linux-gnu" diff --git a/embassy-time-queue-driver/README.md b/embassy-time-queue-driver/README.md deleted file mode 100644 index b9fb12d94..000000000 --- a/embassy-time-queue-driver/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# embassy-time-queue-driver - -This crate contains the driver trait used by the [`embassy-time`](https://crates.io/crates/embassy-time) timer queue. - -You should rarely need to use this crate directly. Only use it when implementing your own timer queue. - -There is two timer queue implementations, one in `embassy-time` enabled by the `generic-queue` feature, and -another in `embassy-executor` enabled by the `integrated-timers` feature. diff --git a/embassy-time-queue-driver/src/lib.rs b/embassy-time-queue-driver/src/lib.rs deleted file mode 100644 index 50736e8c7..000000000 --- a/embassy-time-queue-driver/src/lib.rs +++ /dev/null @@ -1,60 +0,0 @@ -#![no_std] -#![doc = include_str!("../README.md")] -#![warn(missing_docs)] - -//! ## Implementing a timer queue -//! -//! - Define a struct `MyTimerQueue` -//! - Implement [`TimerQueue`] for it -//! - Register it as the global timer queue with [`timer_queue_impl`](crate::timer_queue_impl). -//! -//! ## Example -//! -//! ``` -//! use core::task::Waker; -//! -//! use embassy_time::Instant; -//! use embassy_time::queue::{TimerQueue}; -//! -//! struct MyTimerQueue{}; // not public! -//! -//! impl TimerQueue for MyTimerQueue { -//! fn schedule_wake(&'static self, at: u64, waker: &Waker) { -//! todo!() -//! } -//! } -//! -//! embassy_time_queue_driver::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{}); -//! ``` -use core::task::Waker; - -/// Timer queue -pub trait TimerQueue { - /// Schedules a waker in the queue to be awoken at moment `at`. - /// If this moment is in the past, the waker might be awoken immediately. - fn schedule_wake(&'static self, at: u64, waker: &Waker); -} - -extern "Rust" { - fn _embassy_time_schedule_wake(at: u64, waker: &Waker); -} - -/// Schedule the given waker to be woken at `at`. -pub fn schedule_wake(at: u64, waker: &Waker) { - unsafe { _embassy_time_schedule_wake(at, waker) } -} - -/// Set the TimerQueue implementation. -/// -/// See the module documentation for an example. -#[macro_export] -macro_rules! timer_queue_impl { - (static $name:ident: $t: ty = $val:expr) => { - static $name: $t = $val; - - #[no_mangle] - fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) { - <$t as $crate::TimerQueue>::schedule_wake(&$name, at, waker); - } - }; -} diff --git a/embassy-time-queue-utils/CHANGELOG.md b/embassy-time-queue-utils/CHANGELOG.md new file mode 100644 index 000000000..ae4714f62 --- /dev/null +++ b/embassy-time-queue-utils/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog for embassy-time-queue-utils + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## 0.1.0 - 2024-01-11 + +Initial release diff --git a/embassy-time-queue-utils/Cargo.toml b/embassy-time-queue-utils/Cargo.toml new file mode 100644 index 000000000..48be12118 --- /dev/null +++ b/embassy-time-queue-utils/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "embassy-time-queue-utils" +version = "0.1.0" +edition = "2021" +description = "Timer queue driver trait for embassy-time" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time-queue-utils" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +# Prevent multiple copies of this crate in the same binary. +# Needed because different copies might get different tick rates, causing +# wrong delays if the time driver is using one copy and user code is using another. +# This is especially common when mixing crates from crates.io and git. +links = "embassy-time-queue" + +[dependencies] +heapless = "0.8" +embassy-executor = { version = "0.7.0", path = "../embassy-executor" } + +[features] +#! ### Generic Queue + +#! By default this crate uses a timer queue implementation that is faster but depends on `embassy-executor`. +#! It will panic if you try to await any timer when using another executor. +#! +#! Alternatively, you can choose to use a "generic" timer queue implementation that works on any executor. +#! To enable it, enable any of the features below. +#! +#! The features also set how many timers are used for the generic queue. At most one +#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used. +#! +#! When using embassy-time-queue-driver from libraries, you should *not* enable any `generic-queue-*` feature, to allow the +#! end user to pick. + +## Generic Queue with 8 timers +generic-queue-8 = ["_generic-queue"] +## Generic Queue with 16 timers +generic-queue-16 = ["_generic-queue"] +## Generic Queue with 32 timers +generic-queue-32 = ["_generic-queue"] +## Generic Queue with 64 timers +generic-queue-64 = ["_generic-queue"] +## Generic Queue with 128 timers +generic-queue-128 = ["_generic-queue"] + +_generic-queue = [] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-utils-v$VERSION/embassy-time-queue-utils/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-utils/src/" +target = "x86_64-unknown-linux-gnu" diff --git a/embassy-time-queue-utils/README.md b/embassy-time-queue-utils/README.md new file mode 100644 index 000000000..36461f1cb --- /dev/null +++ b/embassy-time-queue-utils/README.md @@ -0,0 +1,8 @@ +# embassy-time-queue-utils + +This crate contains timer queues to help implementing an [`embassy-time-driver`](https://crates.io/crates/embassy-time-driver). + +As a HAL user, you should not need to depend on this crate. + +As a HAL implementer, you need to depend on this crate if you want to implement a time driver, +but how you should do so is documented in `embassy-time-driver`. diff --git a/embassy-time-queue-driver/build.rs b/embassy-time-queue-utils/build.rs similarity index 100% rename from embassy-time-queue-driver/build.rs rename to embassy-time-queue-utils/build.rs diff --git a/embassy-time-queue-utils/src/lib.rs b/embassy-time-queue-utils/src/lib.rs new file mode 100644 index 000000000..a6f66913f --- /dev/null +++ b/embassy-time-queue-utils/src/lib.rs @@ -0,0 +1,13 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +#[cfg(feature = "_generic-queue")] +pub mod queue_generic; +#[cfg(not(feature = "_generic-queue"))] +pub mod queue_integrated; + +#[cfg(feature = "_generic-queue")] +pub use queue_generic::Queue; +#[cfg(not(feature = "_generic-queue"))] +pub use queue_integrated::Queue; diff --git a/embassy-time-queue-utils/src/queue_generic.rs b/embassy-time-queue-utils/src/queue_generic.rs new file mode 100644 index 000000000..232035bc6 --- /dev/null +++ b/embassy-time-queue-utils/src/queue_generic.rs @@ -0,0 +1,146 @@ +//! Generic timer queue implementations. +//! +//! Time queue drivers may use this to simplify their implementation. + +use core::cmp::{min, Ordering}; +use core::task::Waker; + +use heapless::Vec; + +#[derive(Debug)] +struct Timer { + at: u64, + waker: Waker, +} + +impl PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + self.at == other.at + } +} + +impl Eq for Timer {} + +impl PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + self.at.partial_cmp(&other.at) + } +} + +impl Ord for Timer { + fn cmp(&self, other: &Self) -> Ordering { + self.at.cmp(&other.at) + } +} + +/// A timer queue with a pre-determined capacity. +pub struct ConstGenericQueue { + queue: Vec, +} + +impl ConstGenericQueue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { queue: Vec::new() } + } + + /// Schedules a task to run at a specific time, and returns whether any changes were made. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + self.queue + .iter_mut() + .find(|timer| timer.waker.will_wake(waker)) + .map(|timer| { + if timer.at > at { + timer.at = at; + true + } else { + false + } + }) + .unwrap_or_else(|| { + let mut timer = Timer { + waker: waker.clone(), + at, + }; + + loop { + match self.queue.push(timer) { + Ok(()) => break, + Err(e) => timer = e, + } + + self.queue.pop().unwrap().waker.wake(); + } + + true + }) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&mut self, now: u64) -> u64 { + let mut next_alarm = u64::MAX; + + let mut i = 0; + while i < self.queue.len() { + let timer = &self.queue[i]; + if timer.at <= now { + let timer = self.queue.swap_remove(i); + timer.waker.wake(); + } else { + next_alarm = min(next_alarm, timer.at); + i += 1; + } + } + + next_alarm + } +} + +#[cfg(feature = "generic-queue-8")] +const QUEUE_SIZE: usize = 8; +#[cfg(feature = "generic-queue-16")] +const QUEUE_SIZE: usize = 16; +#[cfg(feature = "generic-queue-32")] +const QUEUE_SIZE: usize = 32; +#[cfg(feature = "generic-queue-64")] +const QUEUE_SIZE: usize = 64; +#[cfg(feature = "generic-queue-128")] +const QUEUE_SIZE: usize = 128; +#[cfg(not(any( + feature = "generic-queue-8", + feature = "generic-queue-16", + feature = "generic-queue-32", + feature = "generic-queue-64", + feature = "generic-queue-128" +)))] +const QUEUE_SIZE: usize = 64; + +/// A timer queue with a pre-determined capacity. +pub struct Queue { + queue: ConstGenericQueue, +} + +impl Queue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { + queue: ConstGenericQueue::new(), + } + } + + /// Schedules a task to run at a specific time, and returns whether any changes were made. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + self.queue.schedule_wake(at, waker) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&mut self, now: u64) -> u64 { + self.queue.next_expiration(now) + } +} diff --git a/embassy-time-queue-utils/src/queue_integrated.rs b/embassy-time-queue-utils/src/queue_integrated.rs new file mode 100644 index 000000000..246cf1d63 --- /dev/null +++ b/embassy-time-queue-utils/src/queue_integrated.rs @@ -0,0 +1,89 @@ +//! Timer queue operations. +use core::cell::Cell; +use core::cmp::min; +use core::task::Waker; + +use embassy_executor::raw::TaskRef; + +/// A timer queue, with items integrated into tasks. +pub struct Queue { + head: Cell>, +} + +impl Queue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { head: Cell::new(None) } + } + + /// Schedules a task to run at a specific time. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + let task = embassy_executor::raw::task_from_waker(waker); + let item = task.timer_queue_item(); + if item.next.get().is_none() { + // If not in the queue, add it and update. + let prev = self.head.replace(Some(task)); + item.next.set(if prev.is_none() { + Some(unsafe { TaskRef::dangling() }) + } else { + prev + }); + item.expires_at.set(at); + true + } else if at <= item.expires_at.get() { + // If expiration is sooner than previously set, update. + item.expires_at.set(at); + true + } else { + // Task does not need to be updated. + false + } + } + + /// Dequeues expired timers and returns the next alarm time. + /// + /// The provided callback will be called for each expired task. Tasks that never expire + /// will be removed, but the callback will not be called. + pub fn next_expiration(&mut self, now: u64) -> u64 { + let mut next_expiration = u64::MAX; + + self.retain(|p| { + let item = p.timer_queue_item(); + let expires = item.expires_at.get(); + + if expires <= now { + // Timer expired, process task. + embassy_executor::raw::wake_task(p); + false + } else { + // Timer didn't yet expire, or never expires. + next_expiration = min(next_expiration, expires); + expires != u64::MAX + } + }); + + next_expiration + } + + fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { + let mut prev = &self.head; + while let Some(p) = prev.get() { + if unsafe { p == TaskRef::dangling() } { + // prev was the last item, stop + break; + } + let item = p.timer_queue_item(); + if f(p) { + // Skip to next + prev = &item.next; + } else { + // Remove it + prev.set(item.next.get()); + item.next.set(None); + } + } + } +} diff --git a/embassy-time/CHANGELOG.md b/embassy-time/CHANGELOG.md index 3b4d93387..09e951ce4 100644 --- a/embassy-time/CHANGELOG.md +++ b/embassy-time/CHANGELOG.md @@ -5,12 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.4.0 - 2025-01-02 + +- `embassy-time-driver` updated from v0.1 to v0.2. +- embassy-time no longer provides an `embassy-time-queue-driver` implementation ## 0.3.2 - 2024-08-05 - Implement with_timeout()/with_deadline() method style call on Future -- Add collapse_debuginfo to fmt.rs macros. +- Add collapse_debuginfo to fmt.rs macros. ## 0.3.1 - 2024-01-11 diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index c3b4e4e3a..76983d880 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-time" -version = "0.3.2" +version = "0.4.0" edition = "2021" description = "Instant and Duration for embedded no-std systems, with async timer support" repository = "https://github.com/embassy-rs/embassy" @@ -24,9 +24,6 @@ target = "x86_64-unknown-linux-gnu" features = ["defmt", "std"] [features] -std = ["tick-hz-1_000_000", "critical-section/std"] -wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000"] - ## Display the time since startup next to defmt log messages. ## At most 1 `defmt-timestamp-uptime-*` feature can be used. ## `defmt-timestamp-uptime` is provided for backwards compatibility (provides the same format as `uptime-us`). @@ -39,31 +36,43 @@ defmt-timestamp-uptime-ts = ["defmt"] defmt-timestamp-uptime-tms = ["defmt"] defmt-timestamp-uptime-tus = ["defmt"] +#! ### Time Drivers + +#! Usually, time drivers are defined by a HAL, or a companion crate to the HAL. For `std` and WASM +#! environments, as well as for testing purposes, `embassy-time` provides some default time drivers +#! that may be suitable for your use case. You can enable one of the following features to use them. + ## Create a `MockDriver` that can be manually advanced for testing purposes. -mock-driver = ["tick-hz-1_000_000"] +mock-driver = ["tick-hz-1_000_000", "dep:embassy-time-queue-utils"] +## Create a time driver for `std` environments. +std = ["tick-hz-1_000_000", "dep:embassy-time-queue-utils"] +## Create a time driver for WASM. +wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000", "dep:embassy-time-queue-utils"] #! ### Generic Queue -## Create a global, generic queue that can be used with any executor. -## To use this you must have a time driver provided. -generic-queue = [] - -#! The following features set how many timers are used for the generic queue. At most one -#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used. +#! By default embassy-time uses a timer queue implementation that is faster but depends on `embassy-executor`. +#! It will panic if you try to await any timer when using another executor. +#! +#! Alternatively, you can choose to use a "generic" timer queue implementation that works on any executor. +#! To enable it, enable any of the features below. +#! +#! The features also set how many timers are used for the generic queue. At most one +#! `generic-queue-*` feature can be enabled. If none is enabled, `queue_integrated` is used. #! #! When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the #! end user to pick. ## Generic Queue with 8 timers -generic-queue-8 = ["generic-queue"] +generic-queue-8 = ["embassy-time-queue-utils/generic-queue-8"] ## Generic Queue with 16 timers -generic-queue-16 = ["generic-queue"] +generic-queue-16 = ["embassy-time-queue-utils/generic-queue-16"] ## Generic Queue with 32 timers -generic-queue-32 = ["generic-queue"] +generic-queue-32 = ["embassy-time-queue-utils/generic-queue-32"] ## Generic Queue with 64 timers -generic-queue-64 = ["generic-queue"] +generic-queue-64 = ["embassy-time-queue-utils/generic-queue-64"] ## Generic Queue with 128 timers -generic-queue-128 = ["generic-queue"] +generic-queue-128 = ["embassy-time-queue-utils/generic-queue-128"] #! ### Tick Rate #! @@ -265,6 +274,8 @@ tick-hz-128_000_000 = ["embassy-time-driver/tick-hz-128_000_000"] tick-hz-130_000_000 = ["embassy-time-driver/tick-hz-130_000_000"] ## 131.072MHz Tick Rate tick-hz-131_072_000 = ["embassy-time-driver/tick-hz-131_072_000"] +## 133.0MHz Tick Rate +tick-hz-133_000_000 = ["embassy-time-driver/tick-hz-133_000_000"] ## 140.0MHz Tick Rate tick-hz-140_000_000 = ["embassy-time-driver/tick-hz-140_000_000"] ## 144.0MHz Tick Rate @@ -406,10 +417,10 @@ tick-hz-5_242_880_000 = ["embassy-time-driver/tick-hz-5_242_880_000"] #! [dependencies] -embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver" } -embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver" } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver" } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true} -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" } @@ -419,7 +430,6 @@ embedded-hal-async = { version = "1.0" } futures-util = { version = "0.3.17", default-features = false } critical-section = "1.1" cfg-if = "1.0.0" -heapless = "0.8" document-features = "0.2.7" @@ -431,4 +441,4 @@ wasm-timer = { version = "0.2.5", optional = true } [dev-dependencies] serial_test = "0.9" critical-section = { version = "1.1", features = ["std"] } -embassy-executor = { version = "0.6.0", path = "../embassy-executor" } +embassy-executor = { version = "0.7.0", path = "../embassy-executor" } diff --git a/embassy-time/src/driver_mock.rs b/embassy-time/src/driver_mock.rs index 8587f9172..bb1961bf2 100644 --- a/embassy-time/src/driver_mock.rs +++ b/embassy-time/src/driver_mock.rs @@ -1,7 +1,9 @@ use core::cell::RefCell; +use core::task::Waker; use critical_section::Mutex as CsMutex; -use embassy_time_driver::{AlarmHandle, Driver}; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; use crate::{Duration, Instant}; @@ -52,29 +54,13 @@ impl MockDriver { /// Advances the time by the specified [`Duration`]. /// Calling any alarm callbacks that are due. pub fn advance(&self, duration: Duration) { - let notify = { - critical_section::with(|cs| { - let mut inner = self.0.borrow_ref_mut(cs); + critical_section::with(|cs| { + let inner = &mut *self.0.borrow_ref_mut(cs); - inner.now += duration; - - let now = inner.now.as_ticks(); - - inner - .alarm - .as_mut() - .filter(|alarm| alarm.timestamp <= now) - .map(|alarm| { - alarm.timestamp = u64::MAX; - - (alarm.callback, alarm.ctx) - }) - }) - }; - - if let Some((callback, ctx)) = notify { - (callback)(ctx); - } + inner.now += duration; + // wake expired tasks. + inner.queue.next_expiration(inner.now.as_ticks()); + }) } } @@ -83,87 +69,37 @@ impl Driver for MockDriver { critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks() } - unsafe fn allocate_alarm(&self) -> Option { + fn schedule_wake(&self, at: u64, waker: &Waker) { critical_section::with(|cs| { - let mut inner = self.0.borrow_ref_mut(cs); - - if inner.alarm.is_some() { - None - } else { - inner.alarm.replace(AlarmState::new()); - - Some(AlarmHandle::new(0)) - } - }) - } - - fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - critical_section::with(|cs| { - let mut inner = self.0.borrow_ref_mut(cs); - - let Some(alarm) = inner.alarm.as_mut() else { - panic!("Alarm not allocated"); - }; - - alarm.callback = callback; - alarm.ctx = ctx; - }); - } - - fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool { - critical_section::with(|cs| { - let mut inner = self.0.borrow_ref_mut(cs); - - if timestamp <= inner.now.as_ticks() { - false - } else { - let Some(alarm) = inner.alarm.as_mut() else { - panic!("Alarm not allocated"); - }; - - alarm.timestamp = timestamp; - true - } + let inner = &mut *self.0.borrow_ref_mut(cs); + // enqueue it + inner.queue.schedule_wake(at, waker); + // wake it if it's in the past. + inner.queue.next_expiration(inner.now.as_ticks()); }) } } struct InnerMockDriver { now: Instant, - alarm: Option, + queue: Queue, } impl InnerMockDriver { const fn new() -> Self { Self { now: Instant::from_ticks(0), - alarm: None, + queue: Queue::new(), } } } -struct AlarmState { - timestamp: u64, - callback: fn(*mut ()), - ctx: *mut (), -} - -impl AlarmState { - const fn new() -> Self { - Self { - timestamp: u64::MAX, - callback: Self::noop, - ctx: core::ptr::null_mut(), - } - } - - fn noop(_ctx: *mut ()) {} -} - -unsafe impl Send for AlarmState {} - #[cfg(test)] mod tests { + use core::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + use std::task::Wake; + use serial_test::serial; use super::*; @@ -185,37 +121,25 @@ mod tests { #[test] #[serial] - fn test_set_alarm_not_in_future() { + fn test_schedule_wake() { setup(); - let driver = MockDriver::get(); - let alarm = unsafe { AlarmHandle::new(0) }; - assert_eq!(false, driver.set_alarm(alarm, driver.now())); - } + static CALLBACK_CALLED: AtomicBool = AtomicBool::new(false); - #[test] - #[serial] - fn test_alarm() { - setup(); + struct MockWaker; + + impl Wake for MockWaker { + fn wake(self: Arc) { + CALLBACK_CALLED.store(true, Ordering::Relaxed); + } + } + let waker = Arc::new(MockWaker).into(); let driver = MockDriver::get(); - let alarm = unsafe { driver.allocate_alarm() }.expect("No alarms available"); - static mut CALLBACK_CALLED: bool = false; - let ctx = &mut () as *mut (); - driver.set_alarm_callback(alarm, |_| unsafe { CALLBACK_CALLED = true }, ctx); - driver.set_alarm(alarm, driver.now() + 1); - assert_eq!(false, unsafe { CALLBACK_CALLED }); + + driver.schedule_wake(driver.now() + 1, &waker); + assert_eq!(false, CALLBACK_CALLED.load(Ordering::Relaxed)); driver.advance(Duration::from_secs(1)); - assert_eq!(true, unsafe { CALLBACK_CALLED }); - } - - #[test] - #[serial] - fn test_allocate_alarm() { - setup(); - - let driver = MockDriver::get(); - assert!(unsafe { driver.allocate_alarm() }.is_some()); - assert!(unsafe { driver.allocate_alarm() }.is_none()); + assert_eq!(true, CALLBACK_CALLED.load(Ordering::Relaxed)); } } diff --git a/embassy-time/src/driver_std.rs b/embassy-time/src/driver_std.rs index d182f8331..87d7ef7eb 100644 --- a/embassy-time/src/driver_std.rs +++ b/embassy-time/src/driver_std.rs @@ -1,160 +1,66 @@ -use core::sync::atomic::{AtomicU8, Ordering}; -use std::cell::{RefCell, UnsafeCell}; -use std::mem::MaybeUninit; -use std::sync::{Condvar, Mutex, Once}; +use std::sync::{Condvar, Mutex}; +use std::thread; use std::time::{Duration as StdDuration, Instant as StdInstant}; -use std::{mem, ptr, thread}; -use critical_section::Mutex as CsMutex; -use embassy_time_driver::{AlarmHandle, Driver}; - -const ALARM_COUNT: usize = 4; - -struct AlarmState { - timestamp: u64, - - // This is really a Option<(fn(*mut ()), *mut ())> - // but fn pointers aren't allowed in const yet - callback: *const (), - ctx: *mut (), -} - -unsafe impl Send for AlarmState {} - -impl AlarmState { - const fn new() -> Self { - Self { - timestamp: u64::MAX, - callback: ptr::null(), - ctx: ptr::null_mut(), - } - } -} +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; struct TimeDriver { - alarm_count: AtomicU8, - - once: Once, - // The STD Driver implementation requires the alarms' mutex to be reentrant, which the STD Mutex isn't - // Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections - // themselves are reentrant - alarms: UninitCell>>, - zero_instant: UninitCell, - signaler: UninitCell, + signaler: Signaler, + inner: Mutex, } -const ALARM_NEW: AlarmState = AlarmState::new(); -embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { - alarm_count: AtomicU8::new(0), +struct Inner { + zero_instant: Option, + queue: Queue, +} - once: Once::new(), - alarms: UninitCell::uninit(), - zero_instant: UninitCell::uninit(), - signaler: UninitCell::uninit(), +embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + inner: Mutex::new(Inner{ + zero_instant: None, + queue: Queue::new(), + }), + signaler: Signaler::new(), }); -impl TimeDriver { - fn init(&self) { - self.once.call_once(|| unsafe { - self.alarms.write(CsMutex::new(RefCell::new([ALARM_NEW; ALARM_COUNT]))); - self.zero_instant.write(StdInstant::now()); - self.signaler.write(Signaler::new()); - - thread::spawn(Self::alarm_thread); - }); - } - - fn alarm_thread() { - let zero = unsafe { DRIVER.zero_instant.read() }; - loop { - let now = DRIVER.now(); - - let next_alarm = critical_section::with(|cs| { - let alarms = unsafe { DRIVER.alarms.as_ref() }.borrow(cs); - loop { - let pending = alarms - .borrow_mut() - .iter_mut() - .find(|alarm| alarm.timestamp <= now) - .map(|alarm| { - alarm.timestamp = u64::MAX; - - (alarm.callback, alarm.ctx) - }); - - if let Some((callback, ctx)) = pending { - // safety: - // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. - // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(callback) }; - f(ctx); - } else { - // No alarm due - break; - } - } - - alarms - .borrow() - .iter() - .map(|alarm| alarm.timestamp) - .min() - .unwrap_or(u64::MAX) - }); - - // Ensure we don't overflow - let until = zero - .checked_add(StdDuration::from_micros(next_alarm)) - .unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1)); - - unsafe { DRIVER.signaler.as_ref() }.wait_until(until); - } +impl Inner { + fn init(&mut self) -> StdInstant { + *self.zero_instant.get_or_insert_with(|| { + thread::spawn(alarm_thread); + StdInstant::now() + }) } } impl Driver for TimeDriver { fn now(&self) -> u64 { - self.init(); - - let zero = unsafe { self.zero_instant.read() }; + let mut inner = self.inner.lock().unwrap(); + let zero = inner.init(); StdInstant::now().duration_since(zero).as_micros() as u64 } - unsafe fn allocate_alarm(&self) -> Option { - let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) - } else { - None - } - }); - - match id { - Ok(id) => Some(AlarmHandle::new(id)), - Err(_) => None, + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + let mut inner = self.inner.lock().unwrap(); + inner.init(); + if inner.queue.schedule_wake(at, waker) { + self.signaler.signal(); } } +} - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - self.init(); - critical_section::with(|cs| { - let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.callback = callback as *const (); - alarm.ctx = ctx; - }); - } +fn alarm_thread() { + let zero = DRIVER.inner.lock().unwrap().zero_instant.unwrap(); + loop { + let now = DRIVER.now(); - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { - self.init(); - critical_section::with(|cs| { - let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.timestamp = timestamp; - unsafe { self.signaler.as_ref() }.signal(); - }); + let next_alarm = DRIVER.inner.lock().unwrap().queue.next_expiration(now); - true + // Ensure we don't overflow + let until = zero + .checked_add(StdDuration::from_micros(next_alarm)) + .unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1)); + + DRIVER.signaler.wait_until(until); } } @@ -164,7 +70,7 @@ struct Signaler { } impl Signaler { - fn new() -> Self { + const fn new() -> Self { Self { mutex: Mutex::new(false), condvar: Condvar::new(), @@ -196,35 +102,3 @@ impl Signaler { self.condvar.notify_one(); } } - -pub(crate) struct UninitCell(MaybeUninit>); -unsafe impl Send for UninitCell {} -unsafe impl Sync for UninitCell {} - -impl UninitCell { - pub const fn uninit() -> Self { - Self(MaybeUninit::uninit()) - } - - pub unsafe fn as_ptr(&self) -> *const T { - (*self.0.as_ptr()).get() - } - - pub unsafe fn as_mut_ptr(&self) -> *mut T { - (*self.0.as_ptr()).get() - } - - pub unsafe fn as_ref(&self) -> &T { - &*self.as_ptr() - } - - pub unsafe fn write(&self, val: T) { - ptr::write(self.as_mut_ptr(), val) - } -} - -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) - } -} diff --git a/embassy-time/src/driver_wasm.rs b/embassy-time/src/driver_wasm.rs index ad884f060..e3207691a 100644 --- a/embassy-time/src/driver_wasm.rs +++ b/embassy-time/src/driver_wasm.rs @@ -1,28 +1,17 @@ -use core::sync::atomic::{AtomicU8, Ordering}; -use std::cell::UnsafeCell; -use std::mem::MaybeUninit; -use std::ptr; -use std::sync::{Mutex, Once}; +use std::sync::Mutex; -use embassy_time_driver::{AlarmHandle, Driver}; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; use wasm_bindgen::prelude::*; use wasm_timer::Instant as StdInstant; -const ALARM_COUNT: usize = 4; - struct AlarmState { token: Option, - closure: Option>, } -unsafe impl Send for AlarmState {} - impl AlarmState { const fn new() -> Self { - Self { - token: None, - closure: None, - } + Self { token: None } } } @@ -33,67 +22,38 @@ extern "C" { } struct TimeDriver { - alarm_count: AtomicU8, - - once: Once, - alarms: UninitCell>, - zero_instant: UninitCell, + inner: Mutex, } -const ALARM_NEW: AlarmState = AlarmState::new(); +struct Inner { + alarm: AlarmState, + zero_instant: Option, + queue: Queue, + closure: Option>, +} + +unsafe impl Send for Inner {} + embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { - alarm_count: AtomicU8::new(0), - once: Once::new(), - alarms: UninitCell::uninit(), - zero_instant: UninitCell::uninit(), + inner: Mutex::new(Inner{ + zero_instant: None, + queue: Queue::new(), + alarm: AlarmState::new(), + closure: None, + }), }); -impl TimeDriver { - fn init(&self) { - self.once.call_once(|| unsafe { - self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); - self.zero_instant.write(StdInstant::now()); - }); - } -} - -impl Driver for TimeDriver { - fn now(&self) -> u64 { - self.init(); - - let zero = unsafe { self.zero_instant.read() }; - StdInstant::now().duration_since(zero).as_micros() as u64 +impl Inner { + fn init(&mut self) -> StdInstant { + *self.zero_instant.get_or_insert_with(StdInstant::now) } - unsafe fn allocate_alarm(&self) -> Option { - let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) - } else { - None - } - }); - - match id { - Ok(id) => Some(AlarmHandle::new(id)), - Err(_) => None, - } + fn now(&mut self) -> u64 { + StdInstant::now().duration_since(self.zero_instant.unwrap()).as_micros() as u64 } - fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { - self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.closure.replace(Closure::new(move || { - callback(ctx); - })); - } - - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { - self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - if let Some(token) = alarm.token { + fn set_alarm(&mut self, timestamp: u64) -> bool { + if let Some(token) = self.alarm.token { clearTimeout(token); } @@ -102,40 +62,42 @@ impl Driver for TimeDriver { false } else { let timeout = (timestamp - now) as u32; - alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); + let closure = self.closure.get_or_insert_with(|| Closure::new(dispatch)); + self.alarm.token = Some(setTimeout(closure, timeout / 1000)); true } } } -pub(crate) struct UninitCell(MaybeUninit>); -unsafe impl Send for UninitCell {} -unsafe impl Sync for UninitCell {} - -impl UninitCell { - pub const fn uninit() -> Self { - Self(MaybeUninit::uninit()) - } - unsafe fn as_ptr(&self) -> *const T { - (*self.0.as_ptr()).get() +impl Driver for TimeDriver { + fn now(&self) -> u64 { + let mut inner = self.inner.lock().unwrap(); + let zero = inner.init(); + StdInstant::now().duration_since(zero).as_micros() as u64 } - pub unsafe fn as_mut_ptr(&self) -> *mut T { - (*self.0.as_ptr()).get() - } - - pub unsafe fn as_ref(&self) -> &T { - &*self.as_ptr() - } - - pub unsafe fn write(&self, val: T) { - ptr::write(self.as_mut_ptr(), val) + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + let mut inner = self.inner.lock().unwrap(); + inner.init(); + if inner.queue.schedule_wake(at, waker) { + let now = inner.now(); + let mut next = inner.queue.next_expiration(now); + while !inner.set_alarm(next) { + let now = inner.now(); + next = inner.queue.next_expiration(now); + } + } } } -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) +fn dispatch() { + let inner = &mut *DRIVER.inner.lock().unwrap(); + + let now = inner.now(); + let mut next = inner.queue.next_expiration(now); + while !inner.set_alarm(next) { + let now = inner.now(); + next = inner.queue.next_expiration(now); } } diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs index 647d208e3..dcda705d3 100644 --- a/embassy-time/src/duration.rs +++ b/embassy-time/src/duration.rs @@ -64,9 +64,9 @@ impl Duration { /// Creates a duration from the specified number of nanoseconds, rounding up. /// NOTE: Delays this small may be inaccurate. - pub const fn from_nanos(micros: u64) -> Duration { + pub const fn from_nanos(nanoseconds: u64) -> Duration { Duration { - ticks: div_ceil(micros * (TICK_HZ / GCD_1G), 1_000_000_000 / GCD_1G), + ticks: div_ceil(nanoseconds * (TICK_HZ / GCD_1G), 1_000_000_000 / GCD_1G), } } @@ -90,6 +90,82 @@ impl Duration { } } + /// Try to create a duration from the specified number of seconds, rounding up. + /// Fails if the number of seconds is too large. + pub const fn try_from_secs(secs: u64) -> Option { + let Some(ticks) = secs.checked_mul(TICK_HZ) else { + return None; + }; + Some(Duration { ticks }) + } + + /// Try to create a duration from the specified number of milliseconds, rounding up. + /// Fails if the number of milliseconds is too large. + pub const fn try_from_millis(millis: u64) -> Option { + let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else { + return None; + }; + Some(Duration { + ticks: div_ceil(value, 1000 / GCD_1K), + }) + } + + /// Try to create a duration from the specified number of microseconds, rounding up. + /// Fails if the number of microseconds is too large. + /// NOTE: Delays this small may be inaccurate. + pub const fn try_from_micros(micros: u64) -> Option { + let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else { + return None; + }; + Some(Duration { + ticks: div_ceil(value, 1_000_000 / GCD_1M), + }) + } + + /// Try to create a duration from the specified number of nanoseconds, rounding up. + /// Fails if the number of nanoseconds is too large. + /// NOTE: Delays this small may be inaccurate. + pub const fn try_from_nanos(nanoseconds: u64) -> Option { + let Some(value) = nanoseconds.checked_mul(TICK_HZ / GCD_1G) else { + return None; + }; + Some(Duration { + ticks: div_ceil(value, 1_000_000_000 / GCD_1G), + }) + } + + /// Try to create a duration from the specified number of seconds, rounding down. + /// Fails if the number of seconds is too large. + pub const fn try_from_secs_floor(secs: u64) -> Option { + let Some(ticks) = secs.checked_mul(TICK_HZ) else { + return None; + }; + Some(Duration { ticks }) + } + + /// Try to create a duration from the specified number of milliseconds, rounding down. + /// Fails if the number of milliseconds is too large. + pub const fn try_from_millis_floor(millis: u64) -> Option { + let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else { + return None; + }; + Some(Duration { + ticks: value / (1000 / GCD_1K), + }) + } + + /// Try to create a duration from the specified number of microseconds, rounding down. + /// Fails if the number of microseconds is too large. + /// NOTE: Delays this small may be inaccurate. + pub const fn try_from_micros_floor(micros: u64) -> Option { + let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else { + return None; + }; + Some(Duration { + ticks: value / (1_000_000 / GCD_1M), + }) + } + /// Creates a duration corresponding to the specified Hz. /// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1 /// tick. Doing so will not deadlock, but will certainly not produce the desired output. diff --git a/embassy-time/src/instant.rs b/embassy-time/src/instant.rs index 909f1b173..6571bea62 100644 --- a/embassy-time/src/instant.rs +++ b/embassy-time/src/instant.rs @@ -17,6 +17,7 @@ impl Instant { pub const MAX: Instant = Instant { ticks: u64::MAX }; /// Returns an Instant representing the current time. + #[inline] pub fn now() -> Instant { Instant { ticks: embassy_time_driver::now(), @@ -49,6 +50,37 @@ impl Instant { } } + /// Try to create an Instant from a microsecond count since system boot. + /// Fails if the number of microseconds is too large. + pub const fn try_from_micros(micros: u64) -> Option { + let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else { + return None; + }; + Some(Self { + ticks: value / (1_000_000 / GCD_1M), + }) + } + + /// Try to create an Instant from a millisecond count since system boot. + /// Fails if the number of milliseconds is too large. + pub const fn try_from_millis(millis: u64) -> Option { + let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else { + return None; + }; + Some(Self { + ticks: value / (1000 / GCD_1K), + }) + } + + /// Try to create an Instant from a second count since system boot. + /// Fails if the number of seconds is too large. + pub const fn try_from_secs(seconds: u64) -> Option { + let Some(ticks) = seconds.checked_mul(TICK_HZ) else { + return None; + }; + Some(Self { ticks }) + } + /// Tick count since system boot. pub const fn as_ticks(&self) -> u64 { self.ticks @@ -114,6 +146,18 @@ impl Instant { pub fn checked_sub(&self, duration: Duration) -> Option { self.ticks.checked_sub(duration.ticks).map(|ticks| Instant { ticks }) } + + /// Adds a Duration to self. In case of overflow, the maximum value is returned. + pub fn saturating_add(mut self, duration: Duration) -> Self { + self.ticks = self.ticks.saturating_add(duration.ticks); + self + } + + /// Subtracts a Duration from self. In case of overflow, the minimum value is returned. + pub fn saturating_sub(mut self, duration: Duration) -> Self { + self.ticks = self.ticks.saturating_sub(duration.ticks); + self + } } impl Add for Instant { diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs index 8d0648ce5..80a359413 100644 --- a/embassy-time/src/lib.rs +++ b/embassy-time/src/lib.rs @@ -25,8 +25,6 @@ pub use driver_mock::MockDriver; mod driver_std; #[cfg(feature = "wasm")] mod driver_wasm; -#[cfg(feature = "generic-queue")] -mod queue_generic; pub use delay::{block_for, Delay}; pub use duration::Duration; diff --git a/embassy-time/src/queue_generic.rs b/embassy-time/src/queue_generic.rs deleted file mode 100644 index 4882afd3e..000000000 --- a/embassy-time/src/queue_generic.rs +++ /dev/null @@ -1,348 +0,0 @@ -use core::cell::RefCell; -use core::cmp::{min, Ordering}; -use core::task::Waker; - -use critical_section::Mutex; -use embassy_time_driver::{allocate_alarm, set_alarm, set_alarm_callback, AlarmHandle}; -use embassy_time_queue_driver::TimerQueue; -use heapless::Vec; - -use crate::Instant; - -#[cfg(feature = "generic-queue-8")] -const QUEUE_SIZE: usize = 8; -#[cfg(feature = "generic-queue-16")] -const QUEUE_SIZE: usize = 16; -#[cfg(feature = "generic-queue-32")] -const QUEUE_SIZE: usize = 32; -#[cfg(feature = "generic-queue-64")] -const QUEUE_SIZE: usize = 64; -#[cfg(feature = "generic-queue-128")] -const QUEUE_SIZE: usize = 128; -#[cfg(not(any( - feature = "generic-queue-8", - feature = "generic-queue-16", - feature = "generic-queue-32", - feature = "generic-queue-64", - feature = "generic-queue-128" -)))] -const QUEUE_SIZE: usize = 64; - -#[derive(Debug)] -struct Timer { - at: Instant, - waker: Waker, -} - -impl PartialEq for Timer { - fn eq(&self, other: &Self) -> bool { - self.at == other.at - } -} - -impl Eq for Timer {} - -impl PartialOrd for Timer { - fn partial_cmp(&self, other: &Self) -> Option { - self.at.partial_cmp(&other.at) - } -} - -impl Ord for Timer { - fn cmp(&self, other: &Self) -> Ordering { - self.at.cmp(&other.at) - } -} - -struct InnerQueue { - queue: Vec, - alarm: AlarmHandle, -} - -impl InnerQueue { - fn schedule_wake(&mut self, at: Instant, waker: &Waker) { - self.queue - .iter_mut() - .find(|timer| timer.waker.will_wake(waker)) - .map(|timer| { - timer.at = min(timer.at, at); - }) - .unwrap_or_else(|| { - let mut timer = Timer { - waker: waker.clone(), - at, - }; - - loop { - match self.queue.push(timer) { - Ok(()) => break, - Err(e) => timer = e, - } - - self.queue.pop().unwrap().waker.wake(); - } - }); - - // Don't wait for the alarm callback to trigger and directly - // dispatch all timers that are already due - // - // Then update the alarm if necessary - self.dispatch(); - } - - fn dispatch(&mut self) { - loop { - let now = Instant::now(); - - let mut next_alarm = Instant::MAX; - - let mut i = 0; - while i < self.queue.len() { - let timer = &self.queue[i]; - if timer.at <= now { - let timer = self.queue.swap_remove(i); - timer.waker.wake(); - } else { - next_alarm = min(next_alarm, timer.at); - i += 1; - } - } - - if self.update_alarm(next_alarm) { - break; - } - } - } - - fn update_alarm(&mut self, next_alarm: Instant) -> bool { - if next_alarm == Instant::MAX { - true - } else { - set_alarm(self.alarm, next_alarm.as_ticks()) - } - } - - fn handle_alarm(&mut self) { - self.dispatch(); - } -} - -struct Queue { - inner: Mutex>>, -} - -impl Queue { - const fn new() -> Self { - Self { - inner: Mutex::new(RefCell::new(None)), - } - } - - fn schedule_wake(&'static self, at: Instant, waker: &Waker) { - critical_section::with(|cs| { - let mut inner = self.inner.borrow_ref_mut(cs); - - if inner.is_none() {} - - inner - .get_or_insert_with(|| { - let handle = unsafe { allocate_alarm() }.unwrap(); - set_alarm_callback(handle, Self::handle_alarm_callback, self as *const _ as _); - InnerQueue { - queue: Vec::new(), - alarm: handle, - } - }) - .schedule_wake(at, waker) - }); - } - - fn handle_alarm(&self) { - critical_section::with(|cs| self.inner.borrow_ref_mut(cs).as_mut().unwrap().handle_alarm()) - } - - fn handle_alarm_callback(ctx: *mut ()) { - unsafe { (ctx as *const Self).as_ref().unwrap() }.handle_alarm(); - } -} - -impl TimerQueue for Queue { - fn schedule_wake(&'static self, at: u64, waker: &Waker) { - Queue::schedule_wake(self, Instant::from_ticks(at), waker); - } -} - -embassy_time_queue_driver::timer_queue_impl!(static QUEUE: Queue = Queue::new()); - -#[cfg(test)] -#[cfg(feature = "mock-driver")] -mod tests { - use core::sync::atomic::{AtomicBool, Ordering}; - use core::task::Waker; - use std::sync::Arc; - use std::task::Wake; - - use serial_test::serial; - - use crate::driver_mock::MockDriver; - use crate::queue_generic::QUEUE; - use crate::{Duration, Instant}; - - struct TestWaker { - pub awoken: AtomicBool, - } - - impl Wake for TestWaker { - fn wake(self: Arc) { - self.awoken.store(true, Ordering::Relaxed); - } - - fn wake_by_ref(self: &Arc) { - self.awoken.store(true, Ordering::Relaxed); - } - } - - fn test_waker() -> (Arc, Waker) { - let arc = Arc::new(TestWaker { - awoken: AtomicBool::new(false), - }); - let waker = Waker::from(arc.clone()); - - (arc, waker) - } - - fn setup() { - MockDriver::get().reset(); - critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None); - } - - fn queue_len() -> usize { - critical_section::with(|cs| { - QUEUE - .inner - .borrow_ref(cs) - .as_ref() - .map(|inner| inner.queue.iter().count()) - .unwrap_or(0) - }) - } - - #[test] - #[serial] - fn test_schedule() { - setup(); - - assert_eq!(queue_len(), 0); - - let (flag, waker) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(1), &waker); - - assert!(!flag.awoken.load(Ordering::Relaxed)); - assert_eq!(queue_len(), 1); - } - - #[test] - #[serial] - fn test_schedule_same() { - setup(); - - let (_flag, waker) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(1), &waker); - - assert_eq!(queue_len(), 1); - - QUEUE.schedule_wake(Instant::from_secs(1), &waker); - - assert_eq!(queue_len(), 1); - - QUEUE.schedule_wake(Instant::from_secs(100), &waker); - - assert_eq!(queue_len(), 1); - - let (_flag2, waker2) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(100), &waker2); - - assert_eq!(queue_len(), 2); - } - - #[test] - #[serial] - fn test_trigger() { - setup(); - - let (flag, waker) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(100), &waker); - - assert!(!flag.awoken.load(Ordering::Relaxed)); - - MockDriver::get().advance(Duration::from_secs(99)); - - assert!(!flag.awoken.load(Ordering::Relaxed)); - - assert_eq!(queue_len(), 1); - - MockDriver::get().advance(Duration::from_secs(1)); - - assert!(flag.awoken.load(Ordering::Relaxed)); - - assert_eq!(queue_len(), 0); - } - - #[test] - #[serial] - fn test_immediate_trigger() { - setup(); - - let (flag, waker) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(100), &waker); - - MockDriver::get().advance(Duration::from_secs(50)); - - let (flag2, waker2) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(40), &waker2); - - assert!(!flag.awoken.load(Ordering::Relaxed)); - assert!(flag2.awoken.load(Ordering::Relaxed)); - assert_eq!(queue_len(), 1); - } - - #[test] - #[serial] - fn test_queue_overflow() { - setup(); - - for i in 1..super::QUEUE_SIZE { - let (flag, waker) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(310), &waker); - - assert_eq!(queue_len(), i); - assert!(!flag.awoken.load(Ordering::Relaxed)); - } - - let (flag, waker) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(300), &waker); - - assert_eq!(queue_len(), super::QUEUE_SIZE); - assert!(!flag.awoken.load(Ordering::Relaxed)); - - let (flag2, waker2) = test_waker(); - - QUEUE.schedule_wake(Instant::from_secs(305), &waker2); - - assert_eq!(queue_len(), super::QUEUE_SIZE); - assert!(flag.awoken.load(Ordering::Relaxed)); - - let (_flag3, waker3) = test_waker(); - QUEUE.schedule_wake(Instant::from_secs(320), &waker3); - assert_eq!(queue_len(), super::QUEUE_SIZE); - assert!(flag2.awoken.load(Ordering::Relaxed)); - } -} diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs index 4d7194b20..d1162eadd 100644 --- a/embassy-time/src/timer.rs +++ b/embassy-time/src/timer.rs @@ -1,8 +1,7 @@ use core::future::{poll_fn, Future}; -use core::pin::{pin, Pin}; +use core::pin::Pin; use core::task::{Context, Poll}; -use futures_util::future::{select, Either}; use futures_util::stream::FusedStream; use futures_util::Stream; @@ -17,11 +16,10 @@ pub struct TimeoutError; /// /// If the future completes before the timeout, 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_timeout(timeout: Duration, fut: F) -> Result { - let timeout_fut = Timer::after(timeout); - match select(pin!(fut), timeout_fut).await { - Either::Left((r, _)) => Ok(r), - Either::Right(_) => Err(TimeoutError), +pub fn with_timeout(timeout: Duration, fut: F) -> TimeoutFuture { + TimeoutFuture { + timer: Timer::after(timeout), + fut, } } @@ -29,16 +27,15 @@ pub async fn with_timeout(timeout: Duration, fut: F) -> Result(at: Instant, fut: F) -> Result { - let timeout_fut = Timer::at(at); - match select(pin!(fut), timeout_fut).await { - Either::Left((r, _)) => Ok(r), - Either::Right(_) => Err(TimeoutError), +pub fn with_deadline(at: Instant, fut: F) -> TimeoutFuture { + TimeoutFuture { + timer: Timer::at(at), + fut, } } /// Provides functions to run a given future with a timeout or a deadline. -pub trait WithTimeout { +pub trait WithTimeout: Sized { /// Output type of the future. type Output; @@ -46,24 +43,50 @@ pub trait WithTimeout { /// /// If the future completes before the timeout, 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. - async fn with_timeout(self, timeout: Duration) -> Result; + fn with_timeout(self, timeout: Duration) -> TimeoutFuture; /// 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. - async fn with_deadline(self, at: Instant) -> Result; + fn with_deadline(self, at: Instant) -> TimeoutFuture; } impl WithTimeout for F { type Output = F::Output; - async fn with_timeout(self, timeout: Duration) -> Result { - with_timeout(timeout, self).await + fn with_timeout(self, timeout: Duration) -> TimeoutFuture { + with_timeout(timeout, self) } - async fn with_deadline(self, at: Instant) -> Result { - with_deadline(at, self).await + fn with_deadline(self, at: Instant) -> TimeoutFuture { + with_deadline(at, self) + } +} + +/// Future for the [`with_timeout`] and [`with_deadline`] functions. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct TimeoutFuture { + timer: Timer, + fut: F, +} + +impl Unpin for TimeoutFuture {} + +impl Future for TimeoutFuture { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let fut = unsafe { Pin::new_unchecked(&mut this.fut) }; + let timer = unsafe { Pin::new_unchecked(&mut this.timer) }; + if let Poll::Ready(x) = fut.poll(cx) { + return Poll::Ready(Ok(x)); + } + if let Poll::Ready(_) = timer.poll(cx) { + return Poll::Ready(Err(TimeoutError)); + } + Poll::Pending } } @@ -157,7 +180,7 @@ impl Future for Timer { if self.yielded_once && self.expires_at <= Instant::now() { Poll::Ready(()) } else { - embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); self.yielded_once = true; Poll::Pending } @@ -200,6 +223,10 @@ impl Future for Timer { /// } /// } /// ``` +/// +/// ## Cancel safety +/// It is safe to cancel waiting for the next tick, +/// meaning no tick is lost if the Future is dropped. pub struct Ticker { expires_at: Instant, duration: Duration, @@ -231,6 +258,9 @@ impl Ticker { } /// Waits for the next tick. + /// + /// ## Cancel safety + /// The produced Future is cancel safe, meaning no tick is lost if the Future is dropped. pub fn next(&mut self) -> impl Future + Send + Sync + '_ { poll_fn(|cx| { if self.expires_at <= Instant::now() { @@ -238,7 +268,7 @@ impl Ticker { self.expires_at += dur; Poll::Ready(()) } else { - embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); Poll::Pending } }) @@ -255,7 +285,7 @@ impl Stream for Ticker { self.expires_at += dur; Poll::Ready(Some(())) } else { - embassy_time_queue_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); Poll::Pending } } diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index 481b2699a..8b2467bca 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -26,20 +26,25 @@ flavors = [ features = ["defmt", "cortex-m", "dfu"] [dependencies] -defmt = { version = "0.3.5", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.17", optional = true } bitflags = "2.4.1" cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } -embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +embassy-boot = { version = "0.4.0", path = "../embassy-boot" } embassy-futures = { version = "0.1.1", path = "../embassy-futures" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.2", path = "../embassy-time" } -embassy-usb = { version = "0.3.0", path = "../embassy-usb", default-features = false } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } +embassy-usb = { version = "0.4.0", path = "../embassy-usb", default-features = false } embedded-storage = { version = "0.3.1" } esp32c3-hal = { version = "0.13.0", optional = true, default-features = false } [features] dfu = [] application = [] -defmt = ["dep:defmt"] +defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-usb/defmt"] +ed25519-dalek = ["embassy-boot/ed25519-dalek", "_verify"] +ed25519-salty = ["embassy-boot/ed25519-salty", "_verify"] + +# Internal features +_verify = [] diff --git a/embassy-usb-dfu/README.md b/embassy-usb-dfu/README.md index bdd5b033a..a81b98279 100644 --- a/embassy-usb-dfu/README.md +++ b/embassy-usb-dfu/README.md @@ -4,3 +4,9 @@ An implementation of the USB DFU 1.1 protocol using embassy-boot. It has 2 compo * DFU protocol mode, enabled by the `dfu` feature. This mode corresponds to the transfer phase DFU protocol described by the USB IF. It supports DFU_DNLOAD requests if marked by the user, and will automatically reset the chip once a DFU transaction has been completed. It also responds to DFU_GETSTATUS, DFU_GETSTATE, DFU_ABORT, and DFU_CLRSTATUS with no user intervention. * DFU runtime mode, enabled by the `application feature`. This mode allows users to expose a DFU interface on their USB device, informing the host of the capability to DFU over USB, and allowing the host to reset the device into its bootloader to complete a DFU operation. Supports DFU_GETSTATUS and DFU_DETACH. When detach/reset is seen by the device as described by the standard, will write a new DFU magic number into the bootloader state in flash, and reset the system. + +## Verification + +Embassy-boot provides functionality to verify that an update binary has been correctly signed using ed25519 as described in https://embassy.dev/book/#_verification. Even though the linked procedure describes the signature being concatenated to the end of the update binary, embassy-boot does not force this and is flexible in terms of how the signature for a binary is distributed. The current implementation in embassy-usb-dfu does however assume that the signature is 64 bytes long and concatenated to the end of the update binary since this is the simplest way to make it work with the usb-dfu mechanism. I.e. embassy-usb-dfu does not currently offer the same flexibility as embassy-boot. + +To enable verification, you need to enable either the `ed25519-dalek` or the `ed25519-salty` feature with `ed25519-salty` being recommended. diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs index f0d7626f6..4b7b72073 100644 --- a/embassy-usb-dfu/src/application.rs +++ b/embassy-usb-dfu/src/application.rs @@ -1,10 +1,8 @@ -use core::marker::PhantomData; - use embassy_boot::BlockingFirmwareState; use embassy_time::{Duration, Instant}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; use embassy_usb::driver::Driver; -use embassy_usb::{Builder, Handler}; +use embassy_usb::{Builder, FunctionBuilder, Handler}; use embedded_storage::nor_flash::NorFlash; use crate::consts::{ @@ -13,31 +11,47 @@ use crate::consts::{ }; use crate::Reset; +/// Generic interface for a system that can signal to the bootloader that USB DFU mode is needed on the next boot. +/// +/// By default this trait is implemented for `BlockingFirmwareState<'d, STATE>` but you could also implement this generic +/// interface yourself instead in more complex situations. This could for instance be when you cannot hand ownership of a +/// `BlockingFirmwareState` instance over directly to the DFU `Control` instance and need to use a more complex mechanism. +pub trait DfuMarker { + /// Signal to the bootloader that DFU mode should be used on the next boot. + fn mark_dfu(&mut self); +} + +impl<'d, STATE: NorFlash> DfuMarker for BlockingFirmwareState<'d, STATE> { + fn mark_dfu(&mut self) { + self.mark_dfu().expect("Failed to mark DFU mode in bootloader") + } +} + /// Internal state for the DFU class -pub struct Control<'d, STATE: NorFlash, RST: Reset> { - firmware_state: BlockingFirmwareState<'d, STATE>, +pub struct Control { + dfu_marker: MARK, attrs: DfuAttributes, state: State, timeout: Option, detach_start: Option, - _rst: PhantomData, + reset: RST, } -impl<'d, STATE: NorFlash, RST: Reset> Control<'d, STATE, RST> { +impl Control { /// Create a new DFU instance to expose a DFU interface. - pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self { + pub fn new(dfu_marker: MARK, attrs: DfuAttributes, reset: RST) -> Self { Control { - firmware_state, + dfu_marker, attrs, state: State::AppIdle, detach_start: None, timeout: None, - _rst: PhantomData, + reset, } } } -impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { +impl Handler for Control { fn reset(&mut self) { if let Some(start) = self.detach_start { let delta = Instant::now() - start; @@ -48,10 +62,8 @@ impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { timeout.as_millis() ); if delta < timeout { - self.firmware_state - .mark_dfu() - .expect("Failed to mark DFU mode in bootloader"); - RST::sys_reset() + self.dfu_marker.mark_dfu(); + self.reset.sys_reset() } } } @@ -69,10 +81,15 @@ impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { match Request::try_from(req.request) { Ok(Request::Detach) => { - trace!("Received DETACH, awaiting USB reset"); self.detach_start = Some(Instant::now()); self.timeout = Some(Duration::from_millis(req.value as u64)); self.state = State::AppDetach; + if self.attrs.contains(DfuAttributes::WILL_DETACH) { + trace!("Received DETACH, performing reset"); + self.reset(); + } else { + trace!("Received DETACH, awaiting USB reset"); + } Some(OutResponse::Accepted) } _ => None, @@ -109,12 +126,18 @@ impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { /// it should expose a DFU device, and a software reset will be issued. /// /// To apply USB DFU updates, the bootloader must be capable of recognizing the DFU magic and exposing a device to handle the full DFU transaction with the host. -pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash, RST: Reset>( +pub fn usb_dfu<'d, D: Driver<'d>, MARK: DfuMarker, RST: Reset>( builder: &mut Builder<'d, D>, - handler: &'d mut Control<'d, STATE, RST>, + handler: &'d mut Control, timeout: Duration, + func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>), ) { let mut func = builder.function(0x00, 0x00, 0x00); + + // Here we give users the opportunity to add their own function level MSOS headers for instance. + // This is useful when DFU functionality is part of a composite USB device. + func_modifier(&mut func); + let mut iface = func.interface(); let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None); let timeout = timeout.as_millis() as u16; diff --git a/embassy-usb-dfu/src/consts.rs b/embassy-usb-dfu/src/consts.rs index f8a056e5c..8701dabc4 100644 --- a/embassy-usb-dfu/src/consts.rs +++ b/embassy-usb-dfu/src/consts.rs @@ -1,85 +1,128 @@ -//! USB DFU constants. +//! USB DFU constants, taken from +//! https://www.usb.org/sites/default/files/DFU_1.1.pdf +/// Device Firmware Upgrade class, App specific pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE; +/// Device Firmware Upgrade subclass, App specific pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01; #[allow(unused)] +/// USB interface alternative setting pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02; #[allow(unused)] +/// DFU runtime class pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01; +/// DFU functional descriptor pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21; -#[cfg(feature = "defmt")] -defmt::bitflags! { - pub struct DfuAttributes: u8 { - const WILL_DETACH = 0b0000_1000; - const MANIFESTATION_TOLERANT = 0b0000_0100; - const CAN_UPLOAD = 0b0000_0010; - const CAN_DOWNLOAD = 0b0000_0001; - } +macro_rules! define_dfu_attributes { + ($macro:path) => { + $macro! { + /// Attributes supported by the DFU controller. + pub struct DfuAttributes: u8 { + /// Generate WillDetache sequence on bus. + const WILL_DETACH = 0b0000_1000; + /// Device can communicate during manifestation phase. + const MANIFESTATION_TOLERANT = 0b0000_0100; + /// Capable of upload. + const CAN_UPLOAD = 0b0000_0010; + /// Capable of download. + const CAN_DOWNLOAD = 0b0000_0001; + } + } + }; } +#[cfg(feature = "defmt")] +define_dfu_attributes!(defmt::bitflags); + #[cfg(not(feature = "defmt"))] -bitflags::bitflags! { - /// Attributes supported by the DFU controller. - pub struct DfuAttributes: u8 { - /// Generate WillDetache sequence on bus. - const WILL_DETACH = 0b0000_1000; - /// Device can communicate during manifestation phase. - const MANIFESTATION_TOLERANT = 0b0000_0100; - /// Capable of upload. - const CAN_UPLOAD = 0b0000_0010; - /// Capable of download. - const CAN_DOWNLOAD = 0b0000_0001; - } -} +define_dfu_attributes!(bitflags::bitflags); #[derive(Copy, Clone, PartialEq, Eq)] #[repr(u8)] #[allow(unused)] +/// An indication of the state that the device is going to enter immediately following transmission of this response. pub(crate) enum State { + /// Device is running its normal application. AppIdle = 0, + /// Device is running its normal application, has received the DFU_DETACH request, and is waiting for a USB reset. AppDetach = 1, + /// Device is operating in the DFU mode and is waiting for requests. DfuIdle = 2, + /// Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS. DlSync = 3, + /// Device is programming a control-write block into its nonvolatile memories. DlBusy = 4, + /// Device is processing a download operation. Expecting DFU_DNLOAD requests. Download = 5, + /// Device has received the final block of firmware from the host, waits for DFU_GETSTATUS to start Manifestation phase or completed this phase ManifestSync = 6, + /// Device is in the Manifestation phase. Not all devices will be able to respond to DFU_GETSTATUS when in this state. Manifest = 7, + /// Device has programmed its memories and is waiting for a USB reset or a power on reset. ManifestWaitReset = 8, + /// The device is processing an upload operation. Expecting DFU_UPLOAD requests. UploadIdle = 9, + /// An error has occurred. Awaiting the DFU_CLRSTATUS request. Error = 10, } #[derive(Copy, Clone, PartialEq, Eq)] #[repr(u8)] #[allow(unused)] +/// An indication of the status resulting from the execution of the most recent request. pub(crate) enum Status { + /// No error Ok = 0x00, + /// File is not targeted for use by this device ErrTarget = 0x01, + /// File is for this device but fails some vendor-specific verification test ErrFile = 0x02, + /// Device is unable to write memory ErrWrite = 0x03, + /// Memory erase function failed ErrErase = 0x04, + /// Memory erase check failed ErrCheckErased = 0x05, + /// Program memory function failed ErrProg = 0x06, + /// Programmed memory failed verification ErrVerify = 0x07, + /// Cannot program memory due to received address that is out of range ErrAddress = 0x08, + /// Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet ErrNotDone = 0x09, + /// Device’s firmware is corrupt. It cannot return to run-time (non-DFU) operations ErrFirmware = 0x0A, + /// iString indicates a vendor-specific error ErrVendor = 0x0B, + /// Device detected unexpected USB reset signaling ErrUsbr = 0x0C, + /// Device detected unexpected power on reset ErrPor = 0x0D, + /// Something went wrong, but the device does not know what ErrUnknown = 0x0E, + /// Device stalled an unexpected request ErrStalledPkt = 0x0F, } #[derive(Copy, Clone, PartialEq, Eq)] #[repr(u8)] +/// DFU requests pub(crate) enum Request { + /// Host instructs the device to generate a detach-attach sequence Detach = 0, + /// Host initiates control-write transfers with this request, and sends a DFU_DNLOAD request + /// with wLength = 0 to indicate that it has completed transferring the firmware image file Dnload = 1, + /// The DFU_UPLOAD request is employed by the host to solicit firmware from the device. Upload = 2, + /// The host employs the DFU_GETSTATUS request to facilitate synchronization with the device. GetStatus = 3, + /// Any time the device detects an error, it waits with transition until ClrStatus ClrStatus = 4, + /// Requests a report about a state of the device GetState = 5, + /// Enables the host to exit from certain states and return to the DFU_IDLE state Abort = 6, } diff --git a/embassy-usb-dfu/src/dfu.rs b/embassy-usb-dfu/src/dfu.rs index e99aa70c3..9a2f125fb 100644 --- a/embassy-usb-dfu/src/dfu.rs +++ b/embassy-usb-dfu/src/dfu.rs @@ -1,9 +1,7 @@ -use core::marker::PhantomData; - -use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater}; +use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterError}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; use embassy_usb::driver::Driver; -use embassy_usb::{Builder, Handler}; +use embassy_usb::{Builder, FunctionBuilder, Handler}; use embedded_storage::nor_flash::{NorFlash, NorFlashErrorKind}; use crate::consts::{ @@ -19,19 +17,32 @@ pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_S state: State, status: Status, offset: usize, - _rst: PhantomData, + buf: AlignedBuffer, + reset: RST, + + #[cfg(feature = "_verify")] + public_key: &'static [u8; 32], } impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> { /// Create a new DFU instance to handle DFU transfers. - pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { + pub fn new( + updater: BlockingFirmwareUpdater<'d, DFU, STATE>, + attrs: DfuAttributes, + reset: RST, + #[cfg(feature = "_verify")] public_key: &'static [u8; 32], + ) -> Self { Self { updater, attrs, state: State::DfuIdle, status: Status::Ok, offset: 0, - _rst: PhantomData, + buf: AlignedBuffer([0; BLOCK_SIZE]), + reset, + + #[cfg(feature = "_verify")] + public_key, } } @@ -42,6 +53,20 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Co } } +impl From for Status { + fn from(e: FirmwareUpdaterError) -> Self { + match e { + FirmwareUpdaterError::Flash(e) => match e { + NorFlashErrorKind::NotAligned => Status::ErrWrite, + NorFlashErrorKind::OutOfBounds => Status::ErrAddress, + _ => Status::ErrUnknown, + }, + FirmwareUpdaterError::Signature(_) => Status::ErrVerify, + FirmwareUpdaterError::BadState => Status::ErrUnknown, + } + } +} + impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Handler for Control<'d, DFU, STATE, RST, BLOCK_SIZE> { @@ -51,65 +76,83 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha data: &[u8], ) -> Option { if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + debug!("Unknown out request: {:?}", req); return None; } match Request::try_from(req.request) { Ok(Request::Abort) => { + info!("Abort requested"); self.reset_state(); Some(OutResponse::Accepted) } Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => { if req.value == 0 { + info!("Download starting"); self.state = State::Download; self.offset = 0; } - let mut buf = AlignedBuffer([0; BLOCK_SIZE]); - buf.as_mut()[..data.len()].copy_from_slice(data); + if self.state != State::Download { + error!("Unexpected DNLOAD while chip is waiting for a GETSTATUS"); + self.status = Status::ErrUnknown; + self.state = State::Error; + return Some(OutResponse::Rejected); + } - if req.length == 0 { - match self.updater.mark_updated() { + if data.len() > BLOCK_SIZE { + error!("USB data len exceeded block size"); + self.status = Status::ErrUnknown; + self.state = State::Error; + return Some(OutResponse::Rejected); + } + + debug!("Copying {} bytes to buffer", data.len()); + self.buf.as_mut()[..data.len()].copy_from_slice(data); + + let final_transfer = req.length == 0; + if final_transfer { + debug!("Receiving final transfer"); + + #[cfg(feature = "_verify")] + let update_res: Result<(), FirmwareUpdaterError> = { + const SIGNATURE_LEN: usize = 64; + + let mut signature = [0; SIGNATURE_LEN]; + let update_len = (self.offset - SIGNATURE_LEN) as u32; + + self.updater.read_dfu(update_len, &mut signature).and_then(|_| { + self.updater + .verify_and_mark_updated(self.public_key, &signature, update_len) + }) + }; + + #[cfg(not(feature = "_verify"))] + let update_res = self.updater.mark_updated(); + + match update_res { Ok(_) => { self.status = Status::Ok; self.state = State::ManifestSync; + info!("Update complete"); } Err(e) => { + error!("Error completing update: {}", e); self.state = State::Error; - match e { - embassy_boot::FirmwareUpdaterError::Flash(e) => match e { - NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, - NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress, - _ => self.status = Status::ErrUnknown, - }, - embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify, - embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown, - } + self.status = e.into(); } } } else { - if self.state != State::Download { - // Unexpected DNLOAD while chip is waiting for a GETSTATUS - self.status = Status::ErrUnknown; - self.state = State::Error; - return Some(OutResponse::Rejected); - } - match self.updater.write_firmware(self.offset, buf.as_ref()) { + debug!("Writing {} bytes at {}", data.len(), self.offset); + match self.updater.write_firmware(self.offset, self.buf.as_ref()) { Ok(_) => { self.status = Status::Ok; self.state = State::DlSync; self.offset += data.len(); } Err(e) => { + error!("Error writing firmware: {:?}", e); self.state = State::Error; - match e { - embassy_boot::FirmwareUpdaterError::Flash(e) => match e { - NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, - NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress, - _ => self.status = Status::ErrUnknown, - }, - embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify, - embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown, - } + self.status = e.into(); } } } @@ -118,6 +161,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha } Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode Ok(Request::ClrStatus) => { + info!("Clear status requested"); self.reset_state(); Some(OutResponse::Accepted) } @@ -131,18 +175,19 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha buf: &'a mut [u8], ) -> Option> { if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + debug!("Unknown in request: {:?}", req); return None; } match Request::try_from(req.request) { Ok(Request::GetStatus) => { - //TODO: Configurable poll timeout, ability to add string for Vendor error - buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); match self.state { State::DlSync => self.state = State::Download, - State::ManifestSync => RST::sys_reset(), + State::ManifestSync => self.reset.sys_reset(), _ => {} } + //TODO: Configurable poll timeout, ability to add string for Vendor error + buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); Some(InResponse::Accepted(&buf[0..6])) } Ok(Request::GetState) => { @@ -150,7 +195,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha Some(InResponse::Accepted(&buf[0..1])) } Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => { - //TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload. + //TODO: FirmwareUpdater provides a way of reading the active partition so we could in theory add functionality to upload firmware. Some(InResponse::Rejected) } _ => None, @@ -168,8 +213,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>( builder: &mut Builder<'d, D>, handler: &'d mut Control<'d, DFU, STATE, RST, BLOCK_SIZE>, + func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>), ) { - let mut func = builder.function(0x00, 0x00, 0x00); + let mut func = builder.function(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU); + + // Here we give users the opportunity to add their own function level MSOS headers for instance. + // This is useful when DFU functionality is part of a composite USB device. + func_modifier(&mut func); + let mut iface = func.interface(); let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None); alt.descriptor( diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs index eaa4b6e33..54ffa7276 100644 --- a/embassy-usb-dfu/src/lib.rs +++ b/embassy-usb-dfu/src/lib.rs @@ -26,10 +26,10 @@ compile_error!("usb-dfu must be compiled with exactly one of `dfu`, or `applicat /// This crate exposes `ResetImmediate` when compiled with cortex-m or esp32c3 support, which immediately issues a /// reset request without interfacing with any other peripherals. /// -/// If alternate behaviour is desired, a custom implementation of Reset can be provided as a type argument to the usb_dfu function. +/// If alternate behaviour is desired, a custom implementation of Reset can be provided as an argument to the usb_dfu function. pub trait Reset { /// Reset the device. - fn sys_reset() -> !; + fn sys_reset(&self); } /// Reset immediately. @@ -38,7 +38,7 @@ pub struct ResetImmediate; #[cfg(feature = "esp32c3-hal")] impl Reset for ResetImmediate { - fn sys_reset() -> ! { + fn sys_reset(&self) { esp32c3_hal::reset::software_reset(); loop {} } @@ -50,7 +50,7 @@ pub struct ResetImmediate; #[cfg(feature = "cortex-m")] impl Reset for ResetImmediate { - fn sys_reset() -> ! { + fn sys_reset(&self) { cortex_m::peripheral::SCB::sys_reset() } } diff --git a/embassy-usb-driver/Cargo.toml b/embassy-usb-driver/Cargo.toml index 41493f00d..e40421649 100644 --- a/embassy-usb-driver/Cargo.toml +++ b/embassy-usb-driver/Cargo.toml @@ -19,4 +19,5 @@ target = "thumbv7em-none-eabi" features = ["defmt"] [dependencies] -defmt = { version = "0.3", optional = true } +embedded-io-async = "0.6.1" +defmt = { version = "1", optional = true } diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs index 3b705c8c4..d204e4d85 100644 --- a/embassy-usb-driver/src/lib.rs +++ b/embassy-usb-driver/src/lib.rs @@ -395,3 +395,12 @@ pub enum EndpointError { /// The endpoint is disabled. Disabled, } + +impl embedded_io_async::Error for EndpointError { + fn kind(&self) -> embedded_io_async::ErrorKind { + match self { + Self::BufferOverflow => embedded_io_async::ErrorKind::OutOfMemory, + Self::Disabled => embedded_io_async::ErrorKind::NotConnected, + } + } +} diff --git a/embassy-usb-logger/CHANGELOG.md b/embassy-usb-logger/CHANGELOG.md index 4cd84b8be..86a9fb032 100644 --- a/embassy-usb-logger/CHANGELOG.md +++ b/embassy-usb-logger/CHANGELOG.md @@ -7,21 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.4.0 - 2025-01-15 + +- Update `embassy-usb` to 0.4.0 + +(skipped v0.3.0 to align version numbers with `embassy-usb`) + ## 0.2.0 - 2024-05-20 -### Added - -- [#2414](https://github.com/embassy-rs/embassy/pull/2414) USB logger can now use an existing USB device (@JomerDev) - -### Changed - - Update `embassy-usb` to 0.2.0 - -### Fixed - -- No more data loss at `Pipe` wraparound -- [#2414](https://github.com/embassy-rs/embassy/pull/2414) Messages that are exactly `MAX_PACKET_SIZE` long are no -longer delayed (@JomerDev) +- Add support for using an existing USB device ([#2414](https://github.com/embassy-rs/embassy/pull/2414), @JomerDev) +- Fix data loss at `Pipe` wraparound +- Messages that are exactly `MAX_PACKET_SIZE` long are no longer delayed ([#2414](https://github.com/embassy-rs/embassy/pull/2414), @JomerDev) ## 0.1.0 - 2024-01-14 diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml index d796e5d8e..6b8e9af47 100644 --- a/embassy-usb-logger/Cargo.toml +++ b/embassy-usb-logger/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-usb-logger" -version = "0.2.0" +version = "0.4.0" edition = "2021" license = "MIT OR Apache-2.0" description = "`log` implementation for USB serial using `embassy-usb`." @@ -15,7 +15,7 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-l target = "thumbv7em-none-eabi" [dependencies] -embassy-usb = { version = "0.3.0", path = "../embassy-usb" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +embassy-usb = { version = "0.4.0", path = "../embassy-usb" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } log = "0.4" diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 34d1ca663..de25abce1 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -3,6 +3,7 @@ #![warn(missing_docs)] use core::fmt::Write as _; +use core::future::Future; use embassy_futures::join::join; use embassy_sync::pipe::Pipe; @@ -13,6 +14,25 @@ use log::{Metadata, Record}; type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// A trait that can be implemented and then passed to the +pub trait ReceiverHandler { + /// Data comes in from the serial port with each command and runs this function + fn handle_data(&self, data: &[u8]) -> impl Future + Send; + + /// Create a new instance of the Handler + fn new() -> Self; +} + +/// Use this Handler if you don't wish to use any handler +pub struct DummyHandler; + +impl ReceiverHandler for DummyHandler { + async fn handle_data(&self, _data: &[u8]) {} + fn new() -> Self { + Self {} + } +} + /// The logger state containing buffers that must live as long as the USB peripheral. pub struct LoggerState<'d> { state: State<'d>, @@ -39,14 +59,34 @@ impl<'d> LoggerState<'d> { pub const MAX_PACKET_SIZE: u8 = 64; /// The logger handle, which contains a pipe with configurable size for buffering log messages. -pub struct UsbLogger { +pub struct UsbLogger { buffer: Pipe, + custom_style: Option) -> ()>, + recieve_handler: Option, } -impl UsbLogger { +impl UsbLogger { /// Create a new logger instance. pub const fn new() -> Self { - Self { buffer: Pipe::new() } + Self { + buffer: Pipe::new(), + custom_style: None, + recieve_handler: None, + } + } + + /// Create a new logger instance with a custom formatter. + pub const fn with_custom_style(custom_style: fn(&Record, &mut Writer<'_, N>) -> ()) -> Self { + Self { + buffer: Pipe::new(), + custom_style: Some(custom_style), + recieve_handler: None, + } + } + + /// Add a command handler to the logger + pub fn with_handler(&mut self, handler: T) { + self.recieve_handler = Some(handler); } /// Run the USB logger using the state and USB driver. Never returns. @@ -62,13 +102,6 @@ impl UsbLogger { config.max_power = 100; config.max_packet_size_0 = MAX_PACKET_SIZE; - // Required for windows compatiblity. - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - let mut builder = Builder::new( driver, config, @@ -106,15 +139,22 @@ impl UsbLogger { } } }; - let discard_fut = async { - let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + let reciever_fut = async { + let mut reciever_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; + let n = receiver.read_packet(&mut reciever_buf).await.unwrap(); + match &self.recieve_handler { + Some(handler) => { + let data = &reciever_buf[..n]; + handler.handle_data(data).await; + } + None => (), + } } }; - join(log_fut, discard_fut).await; + join(log_fut, reciever_fut).await; } /// Creates the futures needed for the logger from a given class @@ -130,21 +170,26 @@ impl UsbLogger { } } -impl log::Log for UsbLogger { +impl log::Log for UsbLogger { fn enabled(&self, _metadata: &Metadata) -> bool { true } fn log(&self, record: &Record) { if self.enabled(record.metadata()) { - let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + if let Some(custom_style) = self.custom_style { + custom_style(record, &mut Writer(&self.buffer)); + } else { + let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + } } } fn flush(&self) {} } -struct Writer<'d, const N: usize>(&'d Pipe); +/// A writer that writes to the USB logger buffer. +pub struct Writer<'d, const N: usize>(&'d Pipe); impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { @@ -165,7 +210,7 @@ impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { /// Initialize and run the USB serial logger, never returns. /// -/// Arguments specify the buffer size, log level and the USB driver, respectively. +/// Arguments specify the buffer size, log level and the USB driver, respectively. You can optionally add a RecieverHandler. /// /// # Usage /// @@ -179,17 +224,27 @@ impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { #[macro_export] macro_rules! run { ( $x:expr, $l:expr, $p:ident ) => { - static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::embassy_usb_logger::UsbLogger::new(); unsafe { let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); } let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; }; + + ( $x:expr, $l:expr, $p:ident, $h:ty ) => { + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new(); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + 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. +/// Arguments specify the buffer size, log level and the serial class, respectively. You can optionally add a RecieverHandler. /// /// # Usage /// @@ -203,10 +258,61 @@ macro_rules! run { #[macro_export] macro_rules! with_class { ( $x:expr, $l:expr, $p:ident ) => {{ - static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::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) }}; + + ( $x:expr, $l:expr, $p:ident, $h:ty ) => {{ + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new(); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + LOGGER.create_future_from_class($p) + } + }}; +} + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// This version of the macro allows for a custom style function to be passed in. +/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer. +/// +/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. You can optionally add a RecieverHandler. +/// +/// # Usage +/// +/// ``` +/// let log_fut = embassy_usb_logger::with_custom_style!(1024, log::LevelFilter::Info, logger_class, |record, writer| { +/// use core::fmt::Write; +/// let level = record.level().as_str(); +/// write!(writer, "[{level}] {}\r\n", record.args()).unwrap(); +/// }); +/// ``` +/// +/// # 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_custom_style { + ( $x:expr, $l:expr, $p:ident, $s:expr ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::embassy_usb_logger::UsbLogger::with_custom_style($s); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + }}; + + ( $x:expr, $l:expr, $p:ident, $s:expr, $h:ty ) => {{ + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = + ::embassy_usb_logger::UsbLogger::with_custom_style($s); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + LOGGER.create_future_from_class($p) + } + }}; } diff --git a/embassy-usb-synopsys-otg/CHANGELOG.md b/embassy-usb-synopsys-otg/CHANGELOG.md new file mode 100644 index 000000000..293363d9a --- /dev/null +++ b/embassy-usb-synopsys-otg/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog for embassy-usb-synopsys-otg + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-12-06 + +- Fix corruption in CONTROL OUT transfers (and remove `quirk_setup_late_cnak`) +- Fix build with `defmt` enabled +- Add USBPHYC clock configuration for H7RS series +- Add support for ISO endpoints +- Add support for a full-speed ULPI mode +- Add OTG core DMA address registers +- Ensure endpoint allocation fails when `endpoint_count < MAX_EP_COUNT`. +- New configuration option: `xcvrdly` (transceiver delay). +- `EpState` now implements `Send` and `Sync`. +- The default value of `vbus_detection` is now `false`. + +## 0.1.0 - 2024-04-30 + +Initial release. diff --git a/embassy-usb-synopsys-otg/Cargo.toml b/embassy-usb-synopsys-otg/Cargo.toml index 68ab0415f..6252feaef 100644 --- a/embassy-usb-synopsys-otg/Cargo.toml +++ b/embassy-usb-synopsys-otg/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-usb-synopsys-otg" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" description = "`embassy-usb-driver` implementation for Synopsys OTG USB controllers" @@ -18,8 +18,8 @@ target = "thumbv7em-none-eabi" [dependencies] critical-section = "1.1" -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } +embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } -defmt = { version = "0.3", optional = true } +defmt = { version = "1.0.1", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-usb-synopsys-otg/README.md b/embassy-usb-synopsys-otg/README.md index 5354f07bf..ba3c8116a 100644 --- a/embassy-usb-synopsys-otg/README.md +++ b/embassy-usb-synopsys-otg/README.md @@ -1,6 +1,6 @@ # Embassy USB driver for the Synopsys USB OTG core -This crate implements [`embassy-usb-driver`](https://crates.io/crates/embassy-usb-driver) for Synopsys USB OTG devices. +This crate implements [`embassy-usb-driver`](https://crates.io/crates/embassy-usb-driver) for Synopsys USB OTG devices. It contains the "core" of the driver that is common across all chips using the Synopsys OTG IP, but it doesn't contain chip-specific initialization such @@ -12,5 +12,5 @@ List of HALs integrating this driver: - [`embassy-stm32`](https://crates.io/crates/embassy-stm32), for STMicroelectronics STM32 chips. - [`esp-hal`](https://crates.io/crates/esp-hal), for Espressif ESP32 chips. -If you wish to integrate this crate into your device's HAL, you will need to add the +If you wish to integrate this crate into your device's HAL, you will need to add the device-specific initialization. See the above crates for examples on how to do it. \ No newline at end of file diff --git a/embassy-usb-synopsys-otg/src/lib.rs b/embassy-usb-synopsys-otg/src/lib.rs index f155f1522..fc4428b54 100644 --- a/embassy-usb-synopsys-otg/src/lib.rs +++ b/embassy-usb-synopsys-otg/src/lib.rs @@ -9,7 +9,7 @@ mod fmt; use core::cell::UnsafeCell; use core::future::poll_fn; use core::marker::PhantomData; -use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, Ordering}; use core::task::Poll; use embassy_sync::waitqueue::AtomicWaker; @@ -18,21 +18,24 @@ use embassy_usb_driver::{ EndpointType, Event, Unsupported, }; +use crate::fmt::Bytes; + pub mod otg_v1; use otg_v1::{regs, vals, Otg}; /// Handle interrupts. -pub unsafe fn on_interrupt( - r: Otg, - state: &State, - ep_count: usize, - quirk_setup_late_cnak: bool, -) { +pub unsafe fn on_interrupt(r: Otg, state: &State, ep_count: usize) { + trace!("irq"); + let ints = r.gintsts().read(); if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() || ints.otgint() || ints.srqint() { // Mask interrupts and notify `Bus` to process them - r.gintmsk().write(|_| {}); + r.gintmsk().write(|w| { + w.set_iepint(true); + w.set_oepint(true); + w.set_rxflvlm(true); + }); state.bus_waker.wake(); } @@ -60,19 +63,9 @@ pub unsafe fn on_interrupt( while r.grstctl().read().txfflsh() {} } - if state.cp_state.setup_ready.load(Ordering::Relaxed) == false { - // SAFETY: exclusive access ensured by atomic bool - let data = unsafe { &mut *state.cp_state.setup_data.get() }; - data[0..4].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); - data[4..8].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); - state.cp_state.setup_ready.store(true, Ordering::Release); - state.ep_states[0].out_waker.wake(); - } else { - error!("received SETUP before previous finished processing"); - // discard FIFO - r.fifo(0).read(); - r.fifo(0).read(); - } + let data = &state.cp_state.setup_data; + data[0].store(r.fifo(0).read().data(), Ordering::Relaxed); + data[1].store(r.fifo(0).read().data(), Ordering::Relaxed); } vals::Pktstsd::OUT_DATA_RX => { trace!("OUT_DATA_RX ep={} len={}", ep_num, len); @@ -106,11 +99,6 @@ pub unsafe fn on_interrupt( } vals::Pktstsd::SETUP_DATA_DONE => { trace!("SETUP_DATA_DONE ep={}", ep_num); - - if quirk_setup_late_cnak { - // Clear NAK to indicate we are ready to receive more data - r.doepctl(ep_num).modify(|w| w.set_cnak(true)); - } } x => trace!("unknown PKTSTS: {}", x.to_bits()), } @@ -147,25 +135,30 @@ pub unsafe fn on_interrupt( } } - // not needed? reception handled in rxflvl - // OUT endpoint interrupt - // if ints.oepint() { - // let mut ep_mask = r.daint().read().oepint(); - // let mut ep_num = 0; + // out endpoint interrupt + if ints.oepint() { + trace!("oepint"); + let mut ep_mask = r.daint().read().oepint(); + let mut ep_num = 0; - // while ep_mask != 0 { - // if ep_mask & 1 != 0 { - // let ep_ints = r.doepint(ep_num).read(); - // // clear all - // r.doepint(ep_num).write_value(ep_ints); - // state.ep_out_wakers[ep_num].wake(); - // trace!("out ep={} irq val={:08x}", ep_num, ep_ints.0); - // } + // Iterate over endpoints while there are non-zero bits in the mask + while ep_mask != 0 { + if ep_mask & 1 != 0 { + let ep_ints = r.doepint(ep_num).read(); + // clear all + r.doepint(ep_num).write_value(ep_ints); - // ep_mask >>= 1; - // ep_num += 1; - // } - // } + if ep_ints.stup() { + state.cp_state.setup_ready.store(true, Ordering::Release); + } + state.ep_states[ep_num].out_waker.wake(); + trace!("out ep={} irq val={:08x}", ep_num, ep_ints.0); + } + + ep_mask >>= 1; + ep_num += 1; + } + } } /// USB PHY type @@ -179,6 +172,8 @@ pub enum PhyType { /// /// Available on a few STM32 chips. InternalHighSpeed, + /// External ULPI Full-Speed PHY (or High-Speed PHY in Full-Speed mode) + ExternalFullSpeed, /// External ULPI High-Speed PHY ExternalHighSpeed, } @@ -188,14 +183,14 @@ impl PhyType { pub fn internal(&self) -> bool { match self { PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, - PhyType::ExternalHighSpeed => false, + PhyType::ExternalHighSpeed | PhyType::ExternalFullSpeed => false, } } /// Get whether this PHY is any of the high-speed types. pub fn high_speed(&self) -> bool { match self { - PhyType::InternalFullSpeed => false, + PhyType::InternalFullSpeed | PhyType::ExternalFullSpeed => false, PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, } } @@ -204,6 +199,7 @@ impl PhyType { match self { PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, + PhyType::ExternalFullSpeed => vals::Dspd::FULL_SPEED_EXTERNAL, PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, } } @@ -229,7 +225,7 @@ unsafe impl Sync for EpState {} struct ControlPipeSetupState { /// Holds received SETUP packets. Available if [Ep0State::setup_ready] is true. - setup_data: UnsafeCell<[u8; 8]>, + setup_data: [AtomicU32; 2], setup_ready: AtomicBool, } @@ -246,23 +242,20 @@ unsafe impl Sync for State {} impl State { /// Create a new State. pub const fn new() -> Self { - const NEW_AW: AtomicWaker = AtomicWaker::new(); - const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _); - const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY); - const NEW_EP_STATE: EpState = EpState { - in_waker: NEW_AW, - out_waker: NEW_AW, - out_buffer: NEW_BUF, - out_size: NEW_SIZE, - }; - Self { cp_state: ControlPipeSetupState { - setup_data: UnsafeCell::new([0u8; 8]), + setup_data: [const { AtomicU32::new(0) }; 2], setup_ready: AtomicBool::new(false), }, - ep_states: [NEW_EP_STATE; EP_COUNT], - bus_waker: NEW_AW, + ep_states: [const { + EpState { + in_waker: AtomicWaker::new(), + out_waker: AtomicWaker::new(), + out_buffer: UnsafeCell::new(0 as _), + out_size: AtomicU16::new(EP_OUT_BUFFER_EMPTY), + } + }; EP_COUNT], + bus_waker: AtomicWaker::new(), } } } @@ -364,7 +357,7 @@ impl<'d, const MAX_EP_COUNT: usize> Driver<'d, MAX_EP_COUNT> { ); if D::dir() == Direction::Out { - if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() { + if self.ep_out_buffer_offset + max_packet_size as usize > self.ep_out_buffer.len() { error!("Not enough endpoint out buffer capacity"); return Err(EndpointAllocError); } @@ -473,7 +466,6 @@ impl<'d, const MAX_EP_COUNT: usize> embassy_usb_driver::Driver<'d> for Driver<'d trace!("start"); let regs = self.instance.regs; - let quirk_setup_late_cnak = self.instance.quirk_setup_late_cnak; let cp_setup_state = &self.instance.state.cp_state; ( Bus { @@ -489,7 +481,6 @@ impl<'d, const MAX_EP_COUNT: usize> embassy_usb_driver::Driver<'d> for Driver<'d ep_out, ep_in, regs, - quirk_setup_late_cnak, }, ) } @@ -581,6 +572,29 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { }); } + /// Applies configuration specific to + /// Core ID 0x0000_5000 + pub fn config_v5(&mut self) { + let r = self.instance.regs; + let phy_type = self.instance.phy_type; + + if phy_type == PhyType::InternalHighSpeed { + r.gccfg_v3().modify(|w| { + w.set_vbvaloven(!self.config.vbus_detection); + w.set_vbvaloval(!self.config.vbus_detection); + w.set_vbden(self.config.vbus_detection); + }); + } else { + r.gotgctl().modify(|w| { + w.set_bvaloen(!self.config.vbus_detection); + w.set_bvaloval(!self.config.vbus_detection); + }); + r.gccfg_v3().modify(|w| { + w.set_vbden(self.config.vbus_detection); + }); + } + } + fn init(&mut self) { let r = self.instance.regs; let phy_type = self.instance.phy_type; @@ -602,6 +616,11 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { w.set_xfrcm(true); }); + // Unmask SETUP received EP interrupt + r.doepmsk().write(|w| { + w.set_stupm(true); + }); + // Unmask and clear core interrupts self.restore_irqs(); r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF)); @@ -713,7 +732,7 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { regs.doeptsiz(index).modify(|w| { w.set_xfrsiz(ep.max_packet_size as _); if index == 0 { - w.set_rxdpid_stupcnt(1); + w.set_rxdpid_stupcnt(3); } else { w.set_pktcnt(1); } @@ -725,8 +744,7 @@ impl<'d, const MAX_EP_COUNT: usize> Bus<'d, MAX_EP_COUNT> { // Enable IRQs for allocated endpoints regs.daintmsk().modify(|w| { w.set_iepm(ep_irq_mask(&self.ep_in)); - // OUT interrupts not used, handled in RXFLVL - // w.set_oepm(ep_irq_mask(&self.ep_out)); + w.set_oepm(ep_irq_mask(&self.ep_out)); }); } @@ -1068,6 +1086,21 @@ impl<'d> embassy_usb_driver::EndpointOut for Endpoint<'d, Out> { w.set_pktcnt(1); }); + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.doepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + // Clear NAK to indicate we are ready to receive more data self.regs.doepctl(index).modify(|w| { w.set_cnak(true); @@ -1085,7 +1118,7 @@ impl<'d> embassy_usb_driver::EndpointOut for Endpoint<'d, Out> { impl<'d> embassy_usb_driver::EndpointIn for Endpoint<'d, In> { async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { - trace!("write ep={:?} data={:?}", self.info.addr, buf); + trace!("write ep={:?} data={:?}", self.info.addr, Bytes(buf)); if buf.len() > self.info.max_packet_size as usize { return Err(EndpointError::BufferOverflow); @@ -1155,6 +1188,21 @@ impl<'d> embassy_usb_driver::EndpointIn for Endpoint<'d, In> { w.set_xfrsiz(buf.len() as _); }); + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.diepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + // Enable endpoint self.regs.diepctl(index).modify(|w| { w.set_cnak(true); @@ -1182,7 +1230,6 @@ pub struct ControlPipe<'d> { setup_state: &'d ControlPipeSetupState, ep_in: Endpoint<'d, In>, ep_out: Endpoint<'d, Out>, - quirk_setup_late_cnak: bool, } impl<'d> embassy_usb_driver::ControlPipe for ControlPipe<'d> { @@ -1195,23 +1242,22 @@ impl<'d> embassy_usb_driver::ControlPipe for ControlPipe<'d> { self.ep_out.state.out_waker.register(cx.waker()); if self.setup_state.setup_ready.load(Ordering::Relaxed) { - let data = unsafe { *self.setup_state.setup_data.get() }; + let mut data = [0; 8]; + data[0..4].copy_from_slice(&self.setup_state.setup_data[0].load(Ordering::Relaxed).to_ne_bytes()); + data[4..8].copy_from_slice(&self.setup_state.setup_data[1].load(Ordering::Relaxed).to_ne_bytes()); self.setup_state.setup_ready.store(false, Ordering::Release); // EP0 should not be controlled by `Bus` so this RMW does not need a critical section - // Receive 1 SETUP packet self.regs.doeptsiz(self.ep_out.info.addr.index()).modify(|w| { - w.set_rxdpid_stupcnt(1); + w.set_rxdpid_stupcnt(3); }); // Clear NAK to indicate we are ready to receive more data - if !self.quirk_setup_late_cnak { - self.regs - .doepctl(self.ep_out.info.addr.index()) - .modify(|w| w.set_cnak(true)); - } + self.regs + .doepctl(self.ep_out.info.addr.index()) + .modify(|w| w.set_cnak(true)); - trace!("SETUP received: {:?}", data); + trace!("SETUP received: {:?}", Bytes(&data)); Poll::Ready(data) } else { trace!("SETUP waiting"); @@ -1224,12 +1270,12 @@ impl<'d> embassy_usb_driver::ControlPipe for ControlPipe<'d> { async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { trace!("control: data_out"); let len = self.ep_out.read(buf).await?; - trace!("control: data_out read: {:?}", &buf[..len]); + trace!("control: data_out read: {:?}", Bytes(&buf[..len])); Ok(len) } async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { - trace!("control: data_in write: {:?}", data); + trace!("control: data_in write: {:?}", Bytes(data)); self.ep_in.write(data).await?; // wait for status response from host after sending the last packet @@ -1329,8 +1375,6 @@ pub struct OtgInstance<'d, const MAX_EP_COUNT: usize> { pub phy_type: PhyType, /// Extra RX FIFO words needed by some implementations. pub extra_rx_fifo_words: u16, - /// Whether to set up late cnak - pub quirk_setup_late_cnak: bool, /// Function to calculate TRDT value based on some internal clock speed. pub calculate_trdt_fn: fn(speed: vals::Dspd) -> u8, } diff --git a/embassy-usb-synopsys-otg/src/otg_v1.rs b/embassy-usb-synopsys-otg/src/otg_v1.rs index 111bc4a63..9e65f2315 100644 --- a/embassy-usb-synopsys-otg/src/otg_v1.rs +++ b/embassy-usb-synopsys-otg/src/otg_v1.rs @@ -108,270 +108,287 @@ impl Otg { } #[doc = "Control and status register"] #[inline(always)] - pub const fn gotgctl(self) -> Reg { + pub fn gotgctl(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0usize) as _) } } #[doc = "Interrupt register"] #[inline(always)] - pub const fn gotgint(self) -> Reg { + pub fn gotgint(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x04usize) as _) } } #[doc = "AHB configuration register"] #[inline(always)] - pub const fn gahbcfg(self) -> Reg { + pub fn gahbcfg(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x08usize) as _) } } #[doc = "USB configuration register"] #[inline(always)] - pub const fn gusbcfg(self) -> Reg { + pub fn gusbcfg(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0cusize) as _) } } #[doc = "Reset register"] #[inline(always)] - pub const fn grstctl(self) -> Reg { + pub fn grstctl(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x10usize) as _) } } #[doc = "Core interrupt register"] #[inline(always)] - pub const fn gintsts(self) -> Reg { + pub fn gintsts(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x14usize) as _) } } #[doc = "Interrupt mask register"] #[inline(always)] - pub const fn gintmsk(self) -> Reg { + pub fn gintmsk(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x18usize) as _) } } #[doc = "Receive status debug read register"] #[inline(always)] - pub const fn grxstsr(self) -> Reg { + pub fn grxstsr(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x1cusize) as _) } } #[doc = "Status read and pop register"] #[inline(always)] - pub const fn grxstsp(self) -> Reg { + pub fn grxstsp(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x20usize) as _) } } #[doc = "Receive FIFO size register"] #[inline(always)] - pub const fn grxfsiz(self) -> Reg { + pub fn grxfsiz(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x24usize) as _) } } #[doc = "Endpoint 0 transmit FIFO size register (device mode)"] #[inline(always)] - pub const fn dieptxf0(self) -> Reg { + pub fn dieptxf0(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x28usize) as _) } } #[doc = "Non-periodic transmit FIFO size register (host mode)"] #[inline(always)] - pub const fn hnptxfsiz(self) -> Reg { + pub fn hnptxfsiz(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x28usize) as _) } } #[doc = "Non-periodic transmit FIFO/queue status register (host mode)"] #[inline(always)] - pub const fn hnptxsts(self) -> Reg { + pub fn hnptxsts(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x2cusize) as _) } } #[doc = "OTG I2C access register"] #[inline(always)] - pub const fn gi2cctl(self) -> Reg { + pub fn gi2cctl(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x30usize) as _) } } #[doc = "General core configuration register, for core_id 0x0000_1xxx"] #[inline(always)] - pub const fn gccfg_v1(self) -> Reg { + pub fn gccfg_v1(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } } #[doc = "General core configuration register, for core_id 0x0000_\\[23\\]xxx"] #[inline(always)] - pub const fn gccfg_v2(self) -> Reg { + pub fn gccfg_v2(self) -> Reg { + unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } + } + #[doc = "General core configuration register, for core_id 0x0000_5xxx"] + #[inline(always)] + pub fn gccfg_v3(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x38usize) as _) } } #[doc = "Core ID register"] #[inline(always)] - pub const fn cid(self) -> Reg { + pub fn cid(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x3cusize) as _) } } #[doc = "OTG core LPM configuration register"] #[inline(always)] - pub const fn glpmcfg(self) -> Reg { + pub fn glpmcfg(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x54usize) as _) } } #[doc = "Host periodic transmit FIFO size register"] #[inline(always)] - pub const fn hptxfsiz(self) -> Reg { + pub fn hptxfsiz(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0100usize) as _) } } #[doc = "Device IN endpoint transmit FIFO size register"] #[inline(always)] - pub const fn dieptxf(self, n: usize) -> Reg { + pub fn dieptxf(self, n: usize) -> Reg { assert!(n < 7usize); unsafe { Reg::from_ptr(self.ptr.add(0x0104usize + n * 4usize) as _) } } #[doc = "Host configuration register"] #[inline(always)] - pub const fn hcfg(self) -> Reg { + pub fn hcfg(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0400usize) as _) } } #[doc = "Host frame interval register"] #[inline(always)] - pub const fn hfir(self) -> Reg { + pub fn hfir(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0404usize) as _) } } #[doc = "Host frame number/frame time remaining register"] #[inline(always)] - pub const fn hfnum(self) -> Reg { + pub fn hfnum(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0408usize) as _) } } #[doc = "Periodic transmit FIFO/queue status register"] #[inline(always)] - pub const fn hptxsts(self) -> Reg { + pub fn hptxsts(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0410usize) as _) } } #[doc = "Host all channels interrupt register"] #[inline(always)] - pub const fn haint(self) -> Reg { + pub fn haint(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0414usize) as _) } } #[doc = "Host all channels interrupt mask register"] #[inline(always)] - pub const fn haintmsk(self) -> Reg { + pub fn haintmsk(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0418usize) as _) } } #[doc = "Host port control and status register"] #[inline(always)] - pub const fn hprt(self) -> Reg { + pub fn hprt(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0440usize) as _) } } #[doc = "Host channel characteristics register"] #[inline(always)] - pub const fn hcchar(self, n: usize) -> Reg { + pub fn hcchar(self, n: usize) -> Reg { assert!(n < 12usize); unsafe { Reg::from_ptr(self.ptr.add(0x0500usize + n * 32usize) as _) } } #[doc = "Host channel split control register"] #[inline(always)] - pub const fn hcsplt(self, n: usize) -> Reg { + pub fn hcsplt(self, n: usize) -> Reg { assert!(n < 12usize); unsafe { Reg::from_ptr(self.ptr.add(0x0504usize + n * 32usize) as _) } } #[doc = "Host channel interrupt register"] #[inline(always)] - pub const fn hcint(self, n: usize) -> Reg { + pub fn hcint(self, n: usize) -> Reg { assert!(n < 12usize); unsafe { Reg::from_ptr(self.ptr.add(0x0508usize + n * 32usize) as _) } } #[doc = "Host channel mask register"] #[inline(always)] - pub const fn hcintmsk(self, n: usize) -> Reg { + pub fn hcintmsk(self, n: usize) -> Reg { assert!(n < 12usize); unsafe { Reg::from_ptr(self.ptr.add(0x050cusize + n * 32usize) as _) } } #[doc = "Host channel transfer size register"] #[inline(always)] - pub const fn hctsiz(self, n: usize) -> Reg { + pub fn hctsiz(self, n: usize) -> Reg { assert!(n < 12usize); unsafe { Reg::from_ptr(self.ptr.add(0x0510usize + n * 32usize) as _) } } + #[doc = "Host channel DMA address register"] + #[inline(always)] + pub fn hcdma(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0514usize + n * 32usize) as _) } + } #[doc = "Device configuration register"] #[inline(always)] - pub const fn dcfg(self) -> Reg { + pub fn dcfg(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0800usize) as _) } } #[doc = "Device control register"] #[inline(always)] - pub const fn dctl(self) -> Reg { + pub fn dctl(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0804usize) as _) } } #[doc = "Device status register"] #[inline(always)] - pub const fn dsts(self) -> Reg { + pub fn dsts(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0808usize) as _) } } #[doc = "Device IN endpoint common interrupt mask register"] #[inline(always)] - pub const fn diepmsk(self) -> Reg { + pub fn diepmsk(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0810usize) as _) } } #[doc = "Device OUT endpoint common interrupt mask register"] #[inline(always)] - pub const fn doepmsk(self) -> Reg { + pub fn doepmsk(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0814usize) as _) } } #[doc = "Device all endpoints interrupt register"] #[inline(always)] - pub const fn daint(self) -> Reg { + pub fn daint(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0818usize) as _) } } #[doc = "All endpoints interrupt mask register"] #[inline(always)] - pub const fn daintmsk(self) -> Reg { + pub fn daintmsk(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x081cusize) as _) } } #[doc = "Device VBUS discharge time register"] #[inline(always)] - pub const fn dvbusdis(self) -> Reg { + pub fn dvbusdis(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0828usize) as _) } } #[doc = "Device VBUS pulsing time register"] #[inline(always)] - pub const fn dvbuspulse(self) -> Reg { + pub fn dvbuspulse(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x082cusize) as _) } } #[doc = "Device IN endpoint FIFO empty interrupt mask register"] #[inline(always)] - pub const fn diepempmsk(self) -> Reg { + pub fn diepempmsk(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0834usize) as _) } } #[doc = "Device IN endpoint control register"] #[inline(always)] - pub const fn diepctl(self, n: usize) -> Reg { + pub fn diepctl(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0900usize + n * 32usize) as _) } } #[doc = "Device IN endpoint interrupt register"] #[inline(always)] - pub const fn diepint(self, n: usize) -> Reg { + pub fn diepint(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0908usize + n * 32usize) as _) } } #[doc = "Device IN endpoint transfer size register"] #[inline(always)] - pub const fn dieptsiz(self, n: usize) -> Reg { + pub fn dieptsiz(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0910usize + n * 32usize) as _) } } #[doc = "Device IN endpoint transmit FIFO status register"] #[inline(always)] - pub const fn dtxfsts(self, n: usize) -> Reg { + pub fn dtxfsts(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0918usize + n * 32usize) as _) } } #[doc = "Device OUT endpoint control register"] #[inline(always)] - pub const fn doepctl(self, n: usize) -> Reg { + pub fn doepctl(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0b00usize + n * 32usize) as _) } } #[doc = "Device OUT endpoint interrupt register"] #[inline(always)] - pub const fn doepint(self, n: usize) -> Reg { + pub fn doepint(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0b08usize + n * 32usize) as _) } } #[doc = "Device OUT endpoint transfer size register"] #[inline(always)] - pub const fn doeptsiz(self, n: usize) -> Reg { + pub fn doeptsiz(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0b10usize + n * 32usize) as _) } } + #[doc = "Device OUT/IN endpoint DMA address register"] + #[inline(always)] + pub fn doepdma(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0b14usize + n * 32usize) as _) } + } #[doc = "Power and clock gating control register"] #[inline(always)] - pub const fn pcgcctl(self) -> Reg { + pub fn pcgcctl(self) -> Reg { unsafe { Reg::from_ptr(self.ptr.add(0x0e00usize) as _) } } #[doc = "Device endpoint / host channel FIFO register"] #[inline(always)] - pub const fn fifo(self, n: usize) -> Reg { + pub fn fifo(self, n: usize) -> Reg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x1000usize + n * 4096usize) as _) } } @@ -783,15 +800,15 @@ pub mod regs { pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); } - #[doc = "SODDFRM/SD1PID"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub const fn soddfrm_sd1pid(&self) -> bool { + pub const fn sd1pid_soddfrm(&self) -> bool { let val = (self.0 >> 29usize) & 0x01; val != 0 } - #[doc = "SODDFRM/SD1PID"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub fn set_soddfrm_sd1pid(&mut self, val: bool) { + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); } #[doc = "EPDIS"] @@ -1162,15 +1179,15 @@ pub mod regs { pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); } - #[doc = "SODDFRM"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub const fn soddfrm(&self) -> bool { + pub const fn sd1pid_soddfrm(&self) -> bool { let val = (self.0 >> 29usize) & 0x01; val != 0 } - #[doc = "SODDFRM"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub fn set_soddfrm(&mut self, val: bool) { + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); } #[doc = "EPDIS"] @@ -1819,6 +1836,172 @@ pub mod regs { GccfgV2(0) } } + #[doc = "OTG general core configuration register."] + #[repr(transparent)] + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct GccfgV3(pub u32); + impl GccfgV3 { + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub const fn chgdet(&self) -> bool { + let val = (self.0 >> 0usize) & 0x01; + val != 0 + } + #[doc = "Charger detection, result of the current mode (primary or secondary)."] + #[inline(always)] + pub fn set_chgdet(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 0usize)) | (((val as u32) & 0x01) << 0usize); + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvplus(&self) -> bool { + let val = (self.0 >> 1usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DP indicator This bit gives the voltage level on DP (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvplus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 1usize)) | (((val as u32) & 0x01) << 1usize); + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub const fn fsvminus(&self) -> bool { + let val = (self.0 >> 2usize) & 0x01; + val != 0 + } + #[doc = "Single-Ended DM indicator This bit gives the voltage level on DM (also result of the comparison with VLGC threshold as defined in BC v1.2 standard)."] + #[inline(always)] + pub fn set_fsvminus(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 2usize)) | (((val as u32) & 0x01) << 2usize); + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub const fn sessvld(&self) -> bool { + let val = (self.0 >> 3usize) & 0x01; + val != 0 + } + #[doc = "VBUS session indicator Indicates if VBUS is above VBUS session threshold."] + #[inline(always)] + pub fn set_sessvld(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 3usize)) | (((val as u32) & 0x01) << 3usize); + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub const fn hcdpen(&self) -> bool { + let val = (self.0 >> 16usize) & 0x01; + val != 0 + } + #[doc = "Host CDP behavior enable."] + #[inline(always)] + pub fn set_hcdpen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 16usize)) | (((val as u32) & 0x01) << 16usize); + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub const fn hcdpdeten(&self) -> bool { + let val = (self.0 >> 17usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port voltage detector enable on DP."] + #[inline(always)] + pub fn set_hcdpdeten(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 17usize)) | (((val as u32) & 0x01) << 17usize); + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub const fn hvdmsrcen(&self) -> bool { + let val = (self.0 >> 18usize) & 0x01; + val != 0 + } + #[doc = "Host CDP port Voltage source enable on DM."] + #[inline(always)] + pub fn set_hvdmsrcen(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 18usize)) | (((val as u32) & 0x01) << 18usize); + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub const fn dcden(&self) -> bool { + let val = (self.0 >> 19usize) & 0x01; + val != 0 + } + #[doc = "Data Contact Detection enable."] + #[inline(always)] + pub fn set_dcden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 19usize)) | (((val as u32) & 0x01) << 19usize); + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub const fn pden(&self) -> bool { + let val = (self.0 >> 20usize) & 0x01; + val != 0 + } + #[doc = "Primary detection enable."] + #[inline(always)] + pub fn set_pden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 20usize)) | (((val as u32) & 0x01) << 20usize); + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub const fn vbden(&self) -> bool { + let val = (self.0 >> 21usize) & 0x01; + val != 0 + } + #[doc = "VBUS detection enable Enables VBUS Sensing Comparators in order to detect VBUS presence and/or perform OTG operation."] + #[inline(always)] + pub fn set_vbden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 21usize)) | (((val as u32) & 0x01) << 21usize); + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub const fn sden(&self) -> bool { + let val = (self.0 >> 22usize) & 0x01; + val != 0 + } + #[doc = "Secondary detection enable."] + #[inline(always)] + pub fn set_sden(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 22usize)) | (((val as u32) & 0x01) << 22usize); + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloval(&self) -> bool { + let val = (self.0 >> 23usize) & 0x01; + val != 0 + } + #[doc = "Software override value of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloval(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 23usize)) | (((val as u32) & 0x01) << 23usize); + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub const fn vbvaloven(&self) -> bool { + let val = (self.0 >> 24usize) & 0x01; + val != 0 + } + #[doc = "Enables a software override of the VBUS B-session detection."] + #[inline(always)] + pub fn set_vbvaloven(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 24usize)) | (((val as u32) & 0x01) << 24usize); + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub const fn forcehostpd(&self) -> bool { + let val = (self.0 >> 25usize) & 0x01; + val != 0 + } + #[doc = "Force host mode pull-downs If the ID pin functions are enabled, the host mode pull-downs on DP and DM activate automatically. However, whenever that is not the case, yet host mode is required, this bit must be used to force the pull-downs active."] + #[inline(always)] + pub fn set_forcehostpd(&mut self, val: bool) { + self.0 = (self.0 & !(0x01 << 25usize)) | (((val as u32) & 0x01) << 25usize); + } + } + impl Default for GccfgV3 { + #[inline(always)] + fn default() -> GccfgV3 { + GccfgV3(0) + } + } #[doc = "I2C access register"] #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq)] diff --git a/embassy-usb/CHANGELOG.md b/embassy-usb/CHANGELOG.md index efdda96fb..76fafed31 100644 --- a/embassy-usb/CHANGELOG.md +++ b/embassy-usb/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.4.0 - 2025-01-15 + +- Change config defaults to to composite with IADs. This ensures embassy-usb Just Works in more cases when using classes with multiple interfaces, or multiple classes. (breaking change) + - `composite_with_iads` = `true` + - `device_class` = `0xEF` + - `device_sub_class` = `0x02` + - `device_protocol` = `0x01` +- Add support for USB Audio Class 1. +- Add support for isochronous endpoints. +- Add support for setting the USB version number. +- Add support for device qualifier descriptors. +- Allow `bos_descriptor_buf` to be a zero length if BOS descriptors aren't used. + ## 0.3.0 - 2024-08-05 - bump usbd-hid from 0.7.0 to 0.8.1 diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index e310d8bae..31fd1c1e0 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-usb" -version = "0.3.0" +version = "0.4.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Async USB device stack for embedded devices in Rust." @@ -48,12 +48,13 @@ max-handler-count-8 = [] [dependencies] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } -embassy-sync = { version = "0.6.0", path = "../embassy-sync" } +embassy-sync = { version = "0.7.0", path = "../embassy-sync" } embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } -defmt = { version = "0.3", optional = true } +defmt = { version = "1", optional = true } log = { version = "0.4.14", optional = true } heapless = "0.8" +embedded-io-async = "0.6.1" # for HID usbd-hid = { version = "0.8.1", optional = true } diff --git a/embassy-usb/README.md b/embassy-usb/README.md index 400fc6695..a053c940b 100644 --- a/embassy-usb/README.md +++ b/embassy-usb/README.md @@ -20,6 +20,11 @@ Async USB device stack for embedded devices in Rust. To add `embassy-usb` support for new hardware (i.e. a new MCU chip), you have to write a driver that implements the [`embassy-usb-driver`](https://crates.io/crates/embassy-usb-driver) traits. +Before writing a new driver, you can first verify whether the chip uses a common USB IP. Several widely used USB IPs already have implementations available, such as: + +- **Synopsys OTG (dwc2)**: Available at [embassy-usb-synopsys-otg](https://crates.io/crates/embassy-usb-synopsys-otg). This IP is used by vendors like STMicroelectronics, Espressif, and others. +- **Musbmhdrc (musb)**: Available at [musb](https://crates.io/crates/musb). This IP is used by vendors like TI, MediaTek, Puya, and others. + Driver crates should depend only on `embassy-usb-driver`, not on the main `embassy-usb` crate. This allows existing drivers to continue working for newer `embassy-usb` major versions, without needing an update, if the driver trait has not had breaking changes. diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 7168e077c..6c4b3f9a4 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,12 +1,23 @@ use heapless::Vec; use crate::config::MAX_HANDLER_COUNT; -use crate::descriptor::{BosWriter, DescriptorWriter}; -use crate::driver::{Driver, Endpoint, EndpointType}; +use crate::descriptor::{BosWriter, DescriptorWriter, SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointInfo, EndpointType}; use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; use crate::types::{InterfaceNumber, StringIndex}; use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +/// Allows Configuring the Bcd USB version below 2.1 +pub enum UsbVersion { + /// Usb version 2.0 + Two = 0x0200, + /// Usb version 2.1 + TwoOne = 0x0210, +} + #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -15,20 +26,28 @@ pub struct Config<'a> { pub(crate) vendor_id: u16, pub(crate) product_id: u16, + /// Device BCD USB version. + /// + /// Default: `0x0210` ("2.1") + pub bcd_usb: UsbVersion, + /// Device class code assigned by USB.org. Set to `0xff` for vendor-specific /// devices that do not conform to any class. /// - /// Default: `0x00` (class code specified by interfaces) + /// Default: `0xEF` + /// See also: `composite_with_iads` pub device_class: u8, /// Device sub-class code. Depends on class. /// - /// Default: `0x00` + /// Default: `0x02` + /// See also: `composite_with_iads` pub device_sub_class: u8, /// Device protocol code. Depends on class and sub-class. /// - /// Default: `0x00` + /// Default: `0x01` + /// See also: `composite_with_iads` pub device_protocol: u8, /// Device release version in BCD. @@ -68,11 +87,14 @@ pub struct Config<'a> { /// Configures the device as a composite device with interface association descriptors. /// - /// If set to `true`, the following fields should have the given values: + /// If set to `true` (default), the following fields should have the given values: /// /// - `device_class` = `0xEF` /// - `device_sub_class` = `0x02` /// - `device_protocol` = `0x01` + /// + /// If set to `false`, those fields must be set correctly for the classes that will be + /// installed on the USB device. pub composite_with_iads: bool, /// Whether the device has its own power source. @@ -101,19 +123,20 @@ impl<'a> Config<'a> { /// Create default configuration with the provided vid and pid values. pub const fn new(vid: u16, pid: u16) -> Self { Self { - device_class: 0x00, - device_sub_class: 0x00, - device_protocol: 0x00, + device_class: 0xEF, + device_sub_class: 0x02, + device_protocol: 0x01, max_packet_size_0: 64, vendor_id: vid, product_id: pid, device_release: 0x0010, + bcd_usb: UsbVersion::TwoOne, manufacturer: None, product: None, serial_number: None, self_powered: false, supports_remote_wakeup: false, - composite_with_iads: false, + composite_with_iads: true, max_power: 100, } } @@ -195,10 +218,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { self.bos_descriptor.end_bos(); // Log the number of allocator bytes actually used in descriptor buffers - info!("USB: config_descriptor used: {}", self.config_descriptor.position()); - info!("USB: bos_descriptor used: {}", self.bos_descriptor.writer.position()); - info!("USB: msos_descriptor used: {}", msos_descriptor.len()); - info!("USB: control_buf size: {}", self.control_buf.len()); + trace!("USB: config_descriptor used: {}", self.config_descriptor.position()); + trace!("USB: bos_descriptor used: {}", self.bos_descriptor.writer.position()); + trace!("USB: msos_descriptor used: {}", msos_descriptor.len()); + trace!("USB: control_buf size: {}", self.control_buf.len()); UsbDevice::build( self.driver, @@ -414,7 +437,7 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn descriptor(&mut self, descriptor_type: u8, descriptor: &[u8]) { - self.builder.config_descriptor.write(descriptor_type, descriptor); + self.builder.config_descriptor.write(descriptor_type, descriptor, &[]); } /// Add a custom Binary Object Store (BOS) descriptor to this alternate setting. @@ -422,26 +445,80 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { self.builder.bos_descriptor.capability(capability_type, capability); } - fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + /// Write a custom endpoint descriptor for a certain endpoint. + /// + /// This can be necessary, if the endpoint descriptors can only be written + /// after the endpoint was created. As an example, an endpoint descriptor + /// may contain the address of an endpoint that was allocated earlier. + pub fn endpoint_descriptor( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { + self.builder + .config_descriptor + .endpoint(endpoint, synchronization_type, usage_type, extra_fields); + } + + /// Allocate an IN endpoint, without writing its descriptor. + /// + /// Used for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { let ep = self .builder .driver .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_in failed"); - self.builder.config_descriptor.endpoint(ep.info()); + ep + } + + fn endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + let ep = self.alloc_endpoint_in(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); ep } - fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + /// Allocate an OUT endpoint, without writing its descriptor. + /// + /// Use for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> D::EndpointOut { let ep = self .builder .driver .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_out failed"); - self.builder.config_descriptor.endpoint(ep.info()); + ep + } + + fn endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + let ep = self.alloc_endpoint_out(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); ep } @@ -451,7 +528,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_bulk_in(&mut self, max_packet_size: u16) -> D::EndpointIn { - self.endpoint_in(EndpointType::Bulk, max_packet_size, 0) + self.endpoint_in( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a BULK OUT endpoint and write its descriptor. @@ -459,7 +543,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_bulk_out(&mut self, max_packet_size: u16) -> D::EndpointOut { - self.endpoint_out(EndpointType::Bulk, max_packet_size, 0) + self.endpoint_out( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a INTERRUPT IN endpoint and write its descriptor. @@ -467,24 +558,66 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms) + self.endpoint_in( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a INTERRUPT OUT endpoint and write its descriptor. pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms) + self.endpoint_out( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a ISOCHRONOUS IN endpoint and write its descriptor. /// /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. - pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms) + pub fn endpoint_isochronous_in( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) } /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. - pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms) + pub fn endpoint_isochronous_out( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) } } diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index 2823e522e..732a433f8 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -1,7 +1,7 @@ //! CDC-ACM class implementation, aka Serial over USB. use core::cell::{Cell, RefCell}; -use core::future::poll_fn; +use core::future::{poll_fn, Future}; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; @@ -47,10 +47,10 @@ impl<'a> Default for State<'a> { impl<'a> State<'a> { /// Create a new `State`. - pub fn new() -> Self { + pub const fn new() -> Self { Self { control: MaybeUninit::uninit(), - shared: ControlShared::default(), + shared: ControlShared::new(), } } } @@ -92,6 +92,12 @@ struct ControlShared { impl Default for ControlShared { fn default() -> Self { + Self::new() + } +} + +impl ControlShared { + const fn new() -> Self { ControlShared { dtr: AtomicBool::new(false), rts: AtomicBool::new(false), @@ -105,10 +111,8 @@ impl Default for ControlShared { changed: AtomicBool::new(false), } } -} -impl ControlShared { - async fn changed(&self) { + fn changed(&self) -> impl Future + '_ { poll_fn(|cx| { if self.changed.load(Ordering::Relaxed) { self.changed.store(false, Ordering::Relaxed); @@ -118,7 +122,6 @@ impl ControlShared { Poll::Pending } }) - .await; } } @@ -407,6 +410,18 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { } } +impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for Sender<'d, D> { + type Error = EndpointError; +} + +impl<'d, D: Driver<'d>> embedded_io_async::Write for Sender<'d, D> { + async fn write(&mut self, buf: &[u8]) -> Result { + let len = core::cmp::min(buf.len(), self.max_packet_size() as usize); + self.write_packet(&buf[..len]).await?; + Ok(len) + } +} + /// CDC ACM class packet receiver. /// /// You can obtain a `Receiver` with [`CdcAcmClass::split`] @@ -448,6 +463,93 @@ impl<'d, D: Driver<'d>> Receiver<'d, D> { pub async fn wait_connection(&mut self) { self.read_ep.wait_enabled().await; } + + /// Turn the `Receiver` into a [`BufferedReceiver`]. + /// + /// The supplied buffer must be large enough to hold max_packet_size bytes. + pub fn into_buffered(self, buf: &'d mut [u8]) -> BufferedReceiver<'d, D> { + BufferedReceiver { + receiver: self, + buffer: buf, + start: 0, + end: 0, + } + } +} + +/// CDC ACM class buffered receiver. +/// +/// It is a requirement of the [`embedded_io_async::Read`] trait that arbitrarily small lengths of +/// data can be read from the stream. The [`Receiver`] can only read full packets at a time. The +/// `BufferedReceiver` instead buffers a single packet if the caller does not read all of the data, +/// so that the remaining data can be returned in subsequent calls. +/// +/// If you have no requirement to use the [`embedded_io_async::Read`] trait or to read a data length +/// less than the packet length, then it is more efficient to use the [`Receiver`] directly. +/// +/// You can obtain a `BufferedReceiver` with [`Receiver::into_buffered`]. +/// +/// [`embedded_io_async::Read`]: https://docs.rs/embedded-io-async/latest/embedded_io_async/trait.Read.html +pub struct BufferedReceiver<'d, D: Driver<'d>> { + receiver: Receiver<'d, D>, + buffer: &'d mut [u8], + start: usize, + end: usize, +} + +impl<'d, D: Driver<'d>> BufferedReceiver<'d, D> { + fn read_from_buffer(&mut self, buf: &mut [u8]) -> usize { + let available = &self.buffer[self.start..self.end]; + let len = core::cmp::min(available.len(), buf.len()); + buf[..len].copy_from_slice(&self.buffer[..len]); + self.start += len; + len + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.receiver.line_coding() + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.receiver.dtr() + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.receiver.rts() + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.receiver.wait_connection().await; + } +} + +impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for BufferedReceiver<'d, D> { + type Error = EndpointError; +} + +impl<'d, D: Driver<'d>> embedded_io_async::Read for BufferedReceiver<'d, D> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + // If there is a buffered packet, return data from that first + if self.start != self.end { + return Ok(self.read_from_buffer(buf)); + } + + // If the caller's buffer is large enough to contain an entire packet, read directly into + // that instead of buffering the packet internally. + if buf.len() > self.receiver.max_packet_size() as usize { + return self.receiver.read_packet(buf).await; + } + + // Otherwise read a packet into the internal buffer, and return some of it to the caller + self.start = 0; + self.end = self.receiver.read_packet(&mut self.buffer).await?; + return Ok(self.read_from_buffer(buf)); + } } /// Number of stop bits for LineCoding diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs index bea9dac27..09d923d2a 100644 --- a/embassy-usb/src/class/cdc_ncm/mod.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -14,9 +14,8 @@ //! This is due to regex spaghetti: //! and this nonsense in the linux kernel: -use core::intrinsics::copy_nonoverlapping; use core::mem::{size_of, MaybeUninit}; -use core::ptr::addr_of; +use core::ptr::{addr_of, copy_nonoverlapping}; use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; diff --git a/embassy-usb/src/class/cmsis_dap_v2.rs b/embassy-usb/src/class/cmsis_dap_v2.rs new file mode 100644 index 000000000..a94e3ddb7 --- /dev/null +++ b/embassy-usb/src/class/cmsis_dap_v2.rs @@ -0,0 +1,128 @@ +//! CMSIS-DAP V2 class implementation. + +use core::mem::MaybeUninit; + +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::StringIndex; +use crate::{msos, Builder, Handler}; + +/// State for the CMSIS-DAP v2 USB class. +pub struct State { + control: MaybeUninit, +} + +struct Control { + iface_string: StringIndex, +} + +impl Handler for Control { + fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> { + if index == self.iface_string { + Some("CMSIS-DAP v2 Interface") + } else { + warn!("unknown string index requested"); + None + } + } +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + } + } +} + +/// USB device class for CMSIS-DAP v2 probes. +pub struct CmsisDapV2Class<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + trace_ep: Option, + max_packet_size: u16, +} + +impl<'d, D: Driver<'d>> CmsisDapV2Class<'d, D> { + /// Creates a new CmsisDapV2Class with the provided UsbBus and `max_packet_size` in bytes. For + /// full-speed devices, `max_packet_size` has to be 64. + /// + /// The `trace` parameter enables the trace output endpoint. This is optional and can be + /// disabled if the probe does not support trace output. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State, max_packet_size: u16, trace: bool) -> Self { + // DAP - Custom Class 0 + let iface_string = builder.string(); + let mut function = builder.function(0xFF, 0, 0); + function.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + function.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + // CMSIS-DAP standard GUID, from https://arm-software.github.io/CMSIS_5/DAP/html/group__DAP__ConfigUSB__gr.html + msos::PropertyData::RegMultiSz(&["{CDB3B5AD-293B-4663-AA36-1AAE46463776}"]), + )); + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0xFF, 0, 0, Some(iface_string)); + let read_ep = alt.endpoint_bulk_out(max_packet_size); + let write_ep = alt.endpoint_bulk_in(max_packet_size); + let trace_ep = if trace { + Some(alt.endpoint_bulk_in(max_packet_size)) + } else { + None + }; + drop(function); + + builder.handler(state.control.write(Control { iface_string })); + + CmsisDapV2Class { + read_ep, + write_ep, + trace_ep, + max_packet_size, + } + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } + + /// Write data to the host. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + for chunk in data.chunks(self.max_packet_size as usize) { + self.write_ep.write(chunk).await?; + } + if data.len() % self.max_packet_size as usize == 0 { + self.write_ep.write(&[]).await?; + } + Ok(()) + } + + /// Write data to the host via the trace output endpoint. + /// + /// Returns `EndpointError::Disabled` if the trace output endpoint is not enabled. + pub async fn write_trace(&mut self, data: &[u8]) -> Result<(), EndpointError> { + let Some(ep) = self.trace_ep.as_mut() else { + return Err(EndpointError::Disabled); + }; + + for chunk in data.chunks(self.max_packet_size as usize) { + ep.write(chunk).await?; + } + if data.len() % self.max_packet_size as usize == 0 { + ep.write(&[]).await?; + } + Ok(()) + } + + /// Read data from the host. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + let mut n = 0; + + loop { + let i = self.read_ep.read(&mut data[n..]).await?; + n += i; + if i < self.max_packet_size as usize { + return Ok(n); + } + } + } +} diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index b883ed4e5..c01707971 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs @@ -1,6 +1,8 @@ //! Implementations of well-known USB classes. pub mod cdc_acm; pub mod cdc_ncm; +pub mod cmsis_dap_v2; pub mod hid; pub mod midi; +pub mod uac1; pub mod web_usb; diff --git a/embassy-usb/src/class/uac1/class_codes.rs b/embassy-usb/src/class/uac1/class_codes.rs new file mode 100644 index 000000000..3f6956771 --- /dev/null +++ b/embassy-usb/src/class/uac1/class_codes.rs @@ -0,0 +1,151 @@ +//! Audio Device Class Codes as defined in Universal Serial Bus Device Class +//! Definition for Audio Devices, Release 1.0, Appendix A and Universal Serial +//! Bus Device Class Definition for Audio Data Formats, Release 1.0, Appendix +//! A.1.1 (Audio Data Format Type I Codes) +#![allow(dead_code)] + +/// The current version of the ADC specification (1.0) +pub const ADC_VERSION: u16 = 0x0100; + +/// The current version of the USB device (1.0) +pub const DEVICE_VERSION: u16 = 0x0100; + +/// Audio Interface Class Code +pub const USB_AUDIO_CLASS: u8 = 0x01; + +// Audio Interface Subclass Codes +pub const USB_UNDEFINED_SUBCLASS: u8 = 0x00; +pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; +pub const USB_AUDIOSTREAMING_SUBCLASS: u8 = 0x02; +pub const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; + +// Audio Protocol Code +pub const PROTOCOL_NONE: u8 = 0x00; + +// Audio Class-Specific Descriptor Types +pub const CS_UNDEFINED: u8 = 0x20; +pub const CS_DEVICE: u8 = 0x21; +pub const CS_CONFIGURATION: u8 = 0x22; +pub const CS_STRING: u8 = 0x23; +pub const CS_INTERFACE: u8 = 0x24; +pub const CS_ENDPOINT: u8 = 0x25; + +// Descriptor Subtype +pub const AC_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const HEADER_SUBTYPE: u8 = 0x01; +pub const INPUT_TERMINAL: u8 = 0x02; +pub const OUTPUT_TERMINAL: u8 = 0x03; +pub const MIXER_UNIT: u8 = 0x04; +pub const SELECTOR_UNIT: u8 = 0x05; +pub const FEATURE_UNIT: u8 = 0x06; +pub const PROCESSING_UNIT: u8 = 0x07; +pub const EXTENSION_UNIT: u8 = 0x08; + +// Audio Class-Specific AS Interface Descriptor Subtypes +pub const AS_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const AS_GENERAL: u8 = 0x01; +pub const FORMAT_TYPE: u8 = 0x02; +pub const FORMAT_SPECIFIC: u8 = 0x03; + +// Processing Unit Process Types +pub const PROCESS_UNDEFINED: u16 = 0x00; +pub const UP_DOWNMIX_PROCESS: u16 = 0x01; +pub const DOLBY_PROLOGIC_PROCESS: u16 = 0x02; +pub const DDD_STEREO_EXTENDER_PROCESS: u16 = 0x03; +pub const REVERBERATION_PROCESS: u16 = 0x04; +pub const CHORUS_PROCESS: u16 = 0x05; +pub const DYN_RANGE_COMP_PROCESS: u16 = 0x06; + +// Audio Class-Specific Endpoint Descriptor Subtypes +pub const EP_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const EP_GENERAL: u8 = 0x01; + +// Audio Class-Specific Request Codes +pub const REQUEST_CODE_UNDEFINED: u8 = 0x00; +pub const SET_CUR: u8 = 0x01; +pub const GET_CUR: u8 = 0x81; +pub const SET_MIN: u8 = 0x02; +pub const GET_MIN: u8 = 0x82; +pub const SET_MAX: u8 = 0x03; +pub const GET_MAX: u8 = 0x83; +pub const SET_RES: u8 = 0x04; +pub const GET_RES: u8 = 0x84; +pub const SET_MEM: u8 = 0x05; +pub const GET_MEM: u8 = 0x85; +pub const GET_STAT: u8 = 0xFF; + +// Terminal Control Selectors +pub const TE_CONTROL_UNDEFINED: u8 = 0x00; +pub const COPY_PROTECT_CONTROL: u8 = 0x01; + +// Feature Unit Control Selectors +pub const FU_CONTROL_UNDEFINED: u8 = 0x00; +pub const MUTE_CONTROL: u8 = 0x01; +pub const VOLUME_CONTROL: u8 = 0x02; +pub const BASS_CONTROL: u8 = 0x03; +pub const MID_CONTROL: u8 = 0x04; +pub const TREBLE_CONTROL: u8 = 0x05; +pub const GRAPHIC_EQUALIZER_CONTROL: u8 = 0x06; +pub const AUTOMATIC_GAIN_CONTROL: u8 = 0x07; +pub const DELAY_CONTROL: u8 = 0x08; +pub const BASS_BOOST_CONTROL: u8 = 0x09; +pub const LOUDNESS_CONTROL: u8 = 0x0A; + +// Up/Down-mix Processing Unit Control Selectors +pub const UD_CONTROL_UNDEFINED: u8 = 0x00; +pub const UD_ENABLE_CONTROL: u8 = 0x01; +pub const UD_MODE_SELECT_CONTROL: u8 = 0x02; + +// Dolby Prologic Processing Unit Control Selectors +pub const DP_CONTROL_UNDEFINED: u8 = 0x00; +pub const DP_ENABLE_CONTROL: u8 = 0x01; +pub const DP_MODE_SELECT_CONTROL: u8 = 0x2; + +// 3D Stereo Extender Processing Unit Control Selectors +pub const DDD_CONTROL_UNDEFINED: u8 = 0x00; +pub const DDD_ENABLE_CONTROL: u8 = 0x01; +pub const DDD_SPACIOUSNESS_CONTROL: u8 = 0x03; + +// Reverberation Processing Unit Control Selectors +pub const RV_CONTROL_UNDEFINED: u8 = 0x00; +pub const RV_ENABLE_CONTROL: u8 = 0x01; +pub const REVERB_LEVEL_CONTROL: u8 = 0x02; +pub const REVERB_TIME_CONTROL: u8 = 0x03; +pub const REVERB_FEEDBACK_CONTROL: u8 = 0x04; + +// Chorus Processing Unit Control Selectors +pub const CH_CONTROL_UNDEFINED: u8 = 0x00; +pub const CH_ENABLE_CONTROL: u8 = 0x01; +pub const CHORUS_LEVEL_CONTROL: u8 = 0x02; +pub const CHORUS_RATE_CONTROL: u8 = 0x03; +pub const CHORUS_DEPTH_CONTROL: u8 = 0x04; + +// Dynamic Range Compressor Processing Unit Control Selectors +pub const DR_CONTROL_UNDEFINED: u8 = 0x00; +pub const DR_ENABLE_CONTROL: u8 = 0x01; +pub const COMPRESSION_RATE_CONTROL: u8 = 0x02; +pub const MAXAMPL_CONTROL: u8 = 0x03; +pub const THRESHOLD_CONTROL: u8 = 0x04; +pub const ATTACK_TIME: u8 = 0x05; +pub const RELEASE_TIME: u8 = 0x06; + +// Extension Unit Control Selectors +pub const XU_CONTROL_UNDEFINED: u16 = 0x00; +pub const XU_ENABLE_CONTROL: u16 = 0x01; + +// Endpoint Control Selectors +pub const EP_CONTROL_UNDEFINED: u8 = 0x00; +pub const SAMPLING_FREQ_CONTROL: u8 = 0x01; +pub const PITCH_CONTROL: u8 = 0x02; + +// Format Type Codes +pub const FORMAT_TYPE_UNDEFINED: u8 = 0x00; +pub const FORMAT_TYPE_I: u8 = 0x01; + +// Audio Data Format Type I Codes +pub const TYPE_I_UNDEFINED: u16 = 0x0000; +pub const PCM: u16 = 0x0001; +pub const PCM8: u16 = 0x0002; +pub const IEEE_FLOAT: u16 = 0x0003; +pub const ALAW: u16 = 0x0004; +pub const MULAW: u16 = 0x0005; diff --git a/embassy-usb/src/class/uac1/mod.rs b/embassy-usb/src/class/uac1/mod.rs new file mode 100644 index 000000000..3d5f4e524 --- /dev/null +++ b/embassy-usb/src/class/uac1/mod.rs @@ -0,0 +1,134 @@ +//! USB Audio Class 1.0 implementations for different applications. +//! +//! Contains: +//! - The `speaker` class with a single audio streaming interface (host to device) + +pub mod speaker; + +mod class_codes; +mod terminal_type; + +/// The maximum supported audio channel index (corresponds to `Top`). +/// FIXME: Use `core::mem::variant_count(...)` when stabilized. +const MAX_AUDIO_CHANNEL_INDEX: usize = 12; + +/// The maximum number of supported audio channels. +/// +/// Includes all twelve channels from `Channel`, plus the Master channel. +const MAX_AUDIO_CHANNEL_COUNT: usize = MAX_AUDIO_CHANNEL_INDEX + 1; + +/// USB Audio Channel +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(missing_docs)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Channel { + LeftFront, + RightFront, + CenterFront, + Lfe, + LeftSurround, + RightSurround, + LeftOfCenter, + RightOfCenter, + Surround, + SideLeft, + SideRight, + Top, +} + +impl Channel { + /// Map a `Channel` to its corresponding USB Audio `ChannelConfig`. + fn get_channel_config(&self) -> ChannelConfig { + match self { + Channel::LeftFront => ChannelConfig::LeftFront, + Channel::RightFront => ChannelConfig::RightFront, + Channel::CenterFront => ChannelConfig::CenterFront, + Channel::Lfe => ChannelConfig::Lfe, + Channel::LeftSurround => ChannelConfig::LeftSurround, + Channel::RightSurround => ChannelConfig::RightSurround, + Channel::LeftOfCenter => ChannelConfig::LeftOfCenter, + Channel::RightOfCenter => ChannelConfig::RightOfCenter, + Channel::Surround => ChannelConfig::Surround, + Channel::SideLeft => ChannelConfig::SideLeft, + Channel::SideRight => ChannelConfig::SideRight, + Channel::Top => ChannelConfig::Top, + } + } +} + +/// USB Audio Channel configuration +#[repr(u16)] +#[non_exhaustive] +// #[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum ChannelConfig { + None = 0x0000, + LeftFront = 0x0001, + RightFront = 0x0002, + CenterFront = 0x0004, + Lfe = 0x0008, + LeftSurround = 0x0010, + RightSurround = 0x0020, + LeftOfCenter = 0x0040, + RightOfCenter = 0x0080, + Surround = 0x0100, + SideLeft = 0x0200, + SideRight = 0x0400, + Top = 0x0800, +} + +impl From for u16 { + fn from(t: ChannelConfig) -> u16 { + t as u16 + } +} + +/// Feedback period adjustment `bRefresh` [UAC 3.7.2.2] +/// +/// From the specification: "A new Ff value is available every 2^(10 – P) frames with P ranging from 1 to 9. The +/// bRefresh field of the synch standard endpoint descriptor is used to report the exponent (10-P) to the Host." +/// +/// This means: +/// - 512 ms (2^9 frames) to 2 ms (2^1 frames) for USB full-speed +/// - 64 ms (2^9 microframes) to 0.25 ms (2^1 microframes) for USB high-speed +#[repr(u8)] +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum FeedbackRefresh { + Period2Frames = 1, + Period4Frames = 2, + Period8Frames = 3, + Period16Frames = 4, + Period32Frames = 5, + Period64Frames = 6, + Period128Frames = 7, + Period256Frames = 8, + Period512Frames = 9, +} + +impl FeedbackRefresh { + /// Gets the number of frames, after which a new feedback frame is returned. + pub const fn frame_count(&self) -> usize { + 1 << (*self as usize) + } +} + +/// Audio sample width. +/// +/// Stored in number of bytes per sample. +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum SampleWidth { + /// 16 bit audio + Width2Byte = 2, + /// 24 bit audio + Width3Byte = 3, + /// 32 bit audio + Width4Byte = 4, +} + +impl SampleWidth { + /// Get the audio sample resolution in number of bit. + pub const fn in_bit(self) -> usize { + 8 * self as usize + } +} diff --git a/embassy-usb/src/class/uac1/speaker.rs b/embassy-usb/src/class/uac1/speaker.rs new file mode 100644 index 000000000..1ff29088c --- /dev/null +++ b/embassy-usb/src/class/uac1/speaker.rs @@ -0,0 +1,777 @@ +//! USB Audio Class 1.0 - Speaker device +//! +//! Provides a class with a single audio streaming interface (host to device), +//! that advertises itself as a speaker. Includes explicit sample rate feedback. +//! +//! Various aspects of the audio stream can be configured, for example: +//! - sample rate +//! - sample resolution +//! - audio channel count and assignment +//! +//! The class provides volume and mute controls for each channel. + +use core::cell::{Cell, RefCell}; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use core::task::Poll; + +use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embassy_sync::waitqueue::WakerRegistration; +use heapless::Vec; + +use super::class_codes::*; +use super::terminal_type::TerminalType; +use super::{Channel, ChannelConfig, FeedbackRefresh, SampleWidth, MAX_AUDIO_CHANNEL_COUNT, MAX_AUDIO_CHANNEL_INDEX}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::descriptor::{SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut, EndpointType}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +/// Maximum allowed sampling rate (3 bytes) in Hz. +const MAX_SAMPLE_RATE_HZ: u32 = 0x7FFFFF; + +/// Arbitrary unique identifier for the input unit. +const INPUT_UNIT_ID: u8 = 0x01; + +/// Arbitrary unique identifier for the feature unit. +const FEATURE_UNIT_ID: u8 = 0x02; + +/// Arbitrary unique identifier for the output unit. +const OUTPUT_UNIT_ID: u8 = 0x03; + +// Volume settings go from -25600 to 0, in steps of 256. +// Therefore, the volume settings are 8q8 values in units of dB. +const VOLUME_STEPS_PER_DB: i16 = 256; +const MIN_VOLUME_DB: i16 = -100; +const MAX_VOLUME_DB: i16 = 0; + +// Maximum number of supported discrete sample rates. +const MAX_SAMPLE_RATE_COUNT: usize = 10; + +/// The volume of an audio channel. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Volume { + /// The channel is muted. + Muted, + /// The channel volume in dB. Ranges from `MIN_VOLUME_DB` (quietest) to `MAX_VOLUME_DB` (loudest). + DeciBel(f32), +} + +/// Internal state for the USB Audio Class. +pub struct State<'d> { + control: Option>, + shared: SharedControl<'d>, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: None, + shared: SharedControl::default(), + } + } +} + +/// Implementation of the USB audio class 1.0. +pub struct Speaker<'d, D: Driver<'d>> { + phantom: PhantomData<&'d D>, +} + +impl<'d, D: Driver<'d>> Speaker<'d, D> { + /// Creates a new [`Speaker`] device, split into a stream, feedback, and a control change notifier. + /// + /// The packet size should be chosen, based on the expected transfer size of samples per (micro)frame. + /// For example, a stereo stream at 32 bit resolution and 48 kHz sample rate yields packets of 384 byte for + /// full-speed USB (1 ms frame interval) or 48 byte for high-speed USB (125 us microframe interval). + /// When using feedback, the packet size varies and thus, the `max_packet_size` should be increased (e.g. to double). + /// + /// # Arguments + /// + /// * `builder` - The builder for the class. + /// * `state` - The internal state of the class. + /// * `max_packet_size` - The maximum packet size per (micro)frame. + /// * `resolution` - The audio sample resolution. + /// * `sample_rates_hz` - The supported sample rates in Hz. + /// * `channels` - The advertised audio channels (up to 12). Entries must be unique, or this function panics. + /// * `feedback_refresh_period` - The refresh period for the feedback value. + pub fn new( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + max_packet_size: u16, + resolution: SampleWidth, + sample_rates_hz: &[u32], + channels: &'d [Channel], + feedback_refresh_period: FeedbackRefresh, + ) -> (Stream<'d, D>, Feedback<'d, D>, ControlMonitor<'d>) { + // The class and subclass fields of the IAD aren't required to match the class and subclass fields of + // the interfaces in the interface collection that the IAD describes. Microsoft recommends that + // the first interface of the collection has class and subclass fields that match the class and + // subclass fields of the IAD. + let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE); + + // Audio control interface (mandatory) [UAC 4.3.1] + let mut interface = func.interface(); + let control_interface = interface.interface_number().into(); + let streaming_interface = u8::from(control_interface) + 1; + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None); + + // Terminal topology: + // Input terminal (receives audio stream) -> Feature Unit (mute and volume) -> Output terminal (e.g. towards speaker) + + // ======================================= + // Input Terminal Descriptor [UAC 3.3.2.1] + // Audio input + let terminal_type: u16 = TerminalType::UsbStreaming.into(); + + // Assemble channel configuration field + let mut channel_config: u16 = ChannelConfig::None.into(); + for channel in channels { + let channel: u16 = channel.get_channel_config().into(); + + if channel_config & channel != 0 { + panic!("Invalid channel config, duplicate channel {}.", channel); + } + channel_config |= channel; + } + + let input_terminal_descriptor = [ + INPUT_TERMINAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + channels.len() as u8, // bNrChannels + channel_config as u8, + (channel_config >> 8) as u8, // wChannelConfig + 0x00, // iChannelNames (none) + 0x00, // iTerminal (none) + ]; + + // ======================================== + // Output Terminal Descriptor [UAC 4.3.2.2] + // Speaker output + let terminal_type: u16 = TerminalType::OutSpeaker.into(); + let output_terminal_descriptor = [ + OUTPUT_TERMINAL, // bDescriptorSubtype + OUTPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + FEATURE_UNIT_ID, // bSourceID (the feature unit) + 0x00, // iTerminal (none) + ]; + + // ===================================== + // Feature Unit Descriptor [UAC 4.3.2.5] + // Mute and volume control + let controls = MUTE_CONTROL | VOLUME_CONTROL; + + const FEATURE_UNIT_DESCRIPTOR_SIZE: usize = 5; + let mut feature_unit_descriptor: Vec = + Vec::from_slice(&[ + FEATURE_UNIT, // bDescriptorSubtype (Feature Unit) + FEATURE_UNIT_ID, // bUnitID + INPUT_UNIT_ID, // bSourceID + 1, // bControlSize (one byte per control) + FU_CONTROL_UNDEFINED, // Master controls (disabled, use only per-channel control) + ]) + .unwrap(); + + // Add per-channel controls + for _channel in channels { + feature_unit_descriptor.push(controls).unwrap(); + } + feature_unit_descriptor.push(0x00).unwrap(); // iFeature (none) + + // =============================================== + // Format desciptor [UAC 4.5.3] + // Used later, for operational streaming interface + let mut format_descriptor: Vec = Vec::from_slice(&[ + FORMAT_TYPE, // bDescriptorSubtype + FORMAT_TYPE_I, // bFormatType + channels.len() as u8, // bNrChannels + resolution as u8, // bSubframeSize + resolution.in_bit() as u8, // bBitResolution + ]) + .unwrap(); + + format_descriptor.push(sample_rates_hz.len() as u8).unwrap(); + + for sample_rate_hz in sample_rates_hz { + assert!(*sample_rate_hz <= MAX_SAMPLE_RATE_HZ); + format_descriptor.push((sample_rate_hz & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 8) & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 16) & 0xFF) as u8).unwrap(); + } + + // ================================================== + // Class-specific AC Interface Descriptor [UAC 4.3.2] + const DESCRIPTOR_HEADER_SIZE: usize = 2; + const INTERFACE_DESCRIPTOR_SIZE: usize = 7; + + let mut total_descriptor_length = 0; + + for size in [ + INTERFACE_DESCRIPTOR_SIZE, + input_terminal_descriptor.len(), + feature_unit_descriptor.len(), + output_terminal_descriptor.len(), + ] { + total_descriptor_length += size + DESCRIPTOR_HEADER_SIZE; + } + + let interface_descriptor: [u8; INTERFACE_DESCRIPTOR_SIZE] = [ + HEADER_SUBTYPE, // bDescriptorSubtype (Header) + ADC_VERSION as u8, + (ADC_VERSION >> 8) as u8, // bcdADC + total_descriptor_length as u8, + (total_descriptor_length >> 8) as u8, // wTotalLength + 0x01, // bInCollection (1 streaming interface) + streaming_interface, // baInterfaceNr + ]; + + alt.descriptor(CS_INTERFACE, &interface_descriptor); + alt.descriptor(CS_INTERFACE, &input_terminal_descriptor); + alt.descriptor(CS_INTERFACE, &feature_unit_descriptor); + alt.descriptor(CS_INTERFACE, &output_terminal_descriptor); + + // ===================================================== + // Audio streaming interface, zero-bandwidth [UAC 4.5.1] + let mut interface = func.interface(); + let alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + drop(alt); + + // ================================================== + // Audio streaming interface, operational [UAC 4.5.1] + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + + alt.descriptor( + CS_INTERFACE, + &[ + AS_GENERAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalLink + 0x00, // bDelay (none) + PCM as u8, + (PCM >> 8) as u8, // wFormatTag (PCM format) + ], + ); + + alt.descriptor(CS_INTERFACE, &format_descriptor); + + let streaming_endpoint = alt.alloc_endpoint_out(EndpointType::Isochronous, max_packet_size, 1); + let feedback_endpoint = alt.alloc_endpoint_in( + EndpointType::Isochronous, + 4, // Feedback packets are 24 bit (10.14 format). + 1, + ); + + // Write the descriptor for the streaming endpoint, after knowing the address of the feedback endpoint. + alt.endpoint_descriptor( + streaming_endpoint.info(), + SynchronizationType::Asynchronous, + UsageType::DataEndpoint, + &[ + 0x00, // bRefresh (0) + feedback_endpoint.info().addr.into(), // bSynchAddress (the feedback endpoint) + ], + ); + + alt.descriptor( + CS_ENDPOINT, + &[ + AS_GENERAL, // bDescriptorSubtype (General) + SAMPLING_FREQ_CONTROL, // bmAttributes (support sampling frequency control) + 0x02, // bLockDelayUnits (PCM) + 0x0000 as u8, + (0x0000 >> 8) as u8, // wLockDelay (0) + ], + ); + + // Write the feedback endpoint descriptor after the streaming endpoint descriptor + // This is demanded by the USB audio class specification. + alt.endpoint_descriptor( + feedback_endpoint.info(), + SynchronizationType::NoSynchronization, + UsageType::FeedbackEndpoint, + &[ + feedback_refresh_period as u8, // bRefresh + 0x00, // bSynchAddress (none) + ], + ); + + // Free up the builder. + drop(func); + + // Store channel information + state.shared.channels = channels; + + state.control = Some(Control { + shared: &state.shared, + streaming_endpoint_address: streaming_endpoint.info().addr.into(), + control_interface_number: control_interface, + }); + + builder.handler(state.control.as_mut().unwrap()); + + let control = &state.shared; + + ( + Stream { streaming_endpoint }, + Feedback { feedback_endpoint }, + ControlMonitor { shared: control }, + ) + } +} + +/// Audio settings for the feature unit. +/// +/// Contains volume and mute control. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AudioSettings { + /// Channel mute states. + muted: [bool; MAX_AUDIO_CHANNEL_COUNT], + /// Channel volume levels in 8.8 format (in dB). + volume_8q8_db: [i16; MAX_AUDIO_CHANNEL_COUNT], +} + +impl Default for AudioSettings { + fn default() -> Self { + AudioSettings { + muted: [false; MAX_AUDIO_CHANNEL_COUNT], + volume_8q8_db: [MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; MAX_AUDIO_CHANNEL_COUNT], + } + } +} + +struct Control<'d> { + control_interface_number: InterfaceNumber, + streaming_endpoint_address: u8, + shared: &'d SharedControl<'d>, +} + +/// Shared data between [`Control`] and the [`Speaker`] class. +struct SharedControl<'d> { + /// The collection of audio settings (volumes, mute states). + audio_settings: CriticalSectionMutex>, + + /// Channel assignments. + channels: &'d [Channel], + + /// The audio sample rate in Hz. + sample_rate_hz: AtomicU32, + + // Notification mechanism. + waker: RefCell, + changed: AtomicBool, +} + +impl<'d> Default for SharedControl<'d> { + fn default() -> Self { + SharedControl { + audio_settings: CriticalSectionMutex::new(Cell::new(AudioSettings::default())), + channels: &[], + sample_rate_hz: AtomicU32::new(0), + waker: RefCell::new(WakerRegistration::new()), + changed: AtomicBool::new(false), + } + } +} + +impl<'d> SharedControl<'d> { + fn changed(&self) -> impl Future + '_ { + poll_fn(|context| { + if self.changed.load(Ordering::Relaxed) { + self.changed.store(false, Ordering::Relaxed); + Poll::Ready(()) + } else { + self.waker.borrow_mut().register(context.waker()); + Poll::Pending + } + }) + } +} + +/// Used for reading audio frames. +pub struct Stream<'d, D: Driver<'d>> { + streaming_endpoint: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Stream<'d, D> { + /// Reads a single packet from the OUT endpoint + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.streaming_endpoint.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.streaming_endpoint.wait_enabled().await; + } +} + +/// Used for writing sample rate information over the feedback endpoint. +pub struct Feedback<'d, D: Driver<'d>> { + feedback_endpoint: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> Feedback<'d, D> { + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.feedback_endpoint.write(data).await + } + + /// Waits for the USB host to enable this interface. + pub async fn wait_connection(&mut self) { + self.feedback_endpoint.wait_enabled().await; + } +} + +/// Control status change monitor +/// +/// Await [`ControlMonitor::changed`] for being notified of configuration changes. Afterwards, the updated +/// configuration settings can be read with [`ControlMonitor::volume`] and [`ControlMonitor::sample_rate_hz`]. +pub struct ControlMonitor<'d> { + shared: &'d SharedControl<'d>, +} + +impl<'d> ControlMonitor<'d> { + fn audio_settings(&self) -> AudioSettings { + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + audio_settings + } + + fn get_logical_channel(&self, search_channel: Channel) -> Option { + let index = self.shared.channels.iter().position(|&c| c == search_channel)?; + + // The logical channels start at one (zero is the master channel). + Some(index + 1) + } + + /// Get the volume of a selected channel. + pub fn volume(&self, channel: Channel) -> Option { + let channel_index = self.get_logical_channel(channel)?; + + if self.audio_settings().muted[channel_index] { + return Some(Volume::Muted); + } + + Some(Volume::DeciBel( + (self.audio_settings().volume_8q8_db[channel_index] as f32) / 256.0f32, + )) + } + + /// Get the streaming endpoint's sample rate in Hz. + pub fn sample_rate_hz(&self) -> u32 { + self.shared.sample_rate_hz.load(Ordering::Relaxed) + } + + /// Return a future for when the control settings change. + pub async fn changed(&self) { + self.shared.changed().await; + } +} + +impl<'d> Control<'d> { + fn changed(&mut self) { + self.shared.changed.store(true, Ordering::Relaxed); + self.shared.waker.borrow_mut().wake(); + } + + fn interface_set_mute_state( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let mute_state = data[0] != 0; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.muted[channel_index as usize] = mute_state; + } + _ => { + debug!("Failed to set channel {} mute state: {}", channel_index, mute_state); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} mute state: {}", channel_index, mute_state); + OutResponse::Accepted + } + + fn interface_set_volume( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let volume = i16::from_ne_bytes(data[..2].try_into().expect("Failed to read volume.")); + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.volume_8q8_db[channel_index as usize] = volume; + } + _ => { + debug!("Failed to set channel {} volume: {}", channel_index, volume); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} volume: {}", channel_index, volume); + OutResponse::Accepted + } + + fn interface_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface set request for interface {}", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + debug!("Unsupported interface set request for entity {}", entity_index); + return Some(OutResponse::Rejected); + } + + if req.request != SET_CUR { + debug!("Unsupported interface set request type {}", req.request); + return Some(OutResponse::Rejected); + } + + let mut audio_settings = self.shared.audio_settings.lock(|x| x.get()); + let response = match control_unit { + MUTE_CONTROL => self.interface_set_mute_state(&mut audio_settings, channel_index, data), + VOLUME_CONTROL => self.interface_set_volume(&mut audio_settings, channel_index, data), + _ => OutResponse::Rejected, + }; + + if response == OutResponse::Rejected { + return Some(response); + } + + // Store updated settings + self.shared.audio_settings.lock(|x| x.set(audio_settings)); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn endpoint_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!( + "Unhandled endpoint set request for endpoint {} and control {} with data {:?}", + endpoint_address, control_selector, data + ); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL { + debug!( + "Unsupported endpoint set request for control selector {}", + control_selector + ); + return Some(OutResponse::Rejected); + } + + let sample_rate_hz: u32 = (data[0] as u32) | (data[1] as u32) << 8 | (data[2] as u32) << 16; + self.shared.sample_rate_hz.store(sample_rate_hz, Ordering::Relaxed); + + debug!("Set endpoint {} sample rate to {} Hz", endpoint_address, sample_rate_hz); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn interface_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface get request for interface {}.", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + // Only this function unit can be handled at the moment. + debug!("Unsupported interface get request for entity {}.", entity_index); + return Some(InResponse::Rejected); + } + + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + match req.request { + GET_CUR => match control_unit { + VOLUME_CONTROL => { + let volume: i16; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => volume = audio_settings.volume_8q8_db[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = volume as u8; + buf[1] = (volume >> 8) as u8; + + debug!("Got channel {} volume: {}.", channel_index, volume); + return Some(InResponse::Accepted(&buf[..2])); + } + MUTE_CONTROL => { + let mute_state: bool; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => mute_state = audio_settings.muted[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = mute_state.into(); + debug!("Got channel {} mute state: {}.", channel_index, mute_state); + return Some(InResponse::Accepted(&buf[..1])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MIN => match control_unit { + VOLUME_CONTROL => { + let min_volume = MIN_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = min_volume as u8; + buf[1] = (min_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MAX => match control_unit { + VOLUME_CONTROL => { + let max_volume = MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = max_volume as u8; + buf[1] = (max_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_RES => match control_unit { + VOLUME_CONTROL => { + buf[0] = VOLUME_STEPS_PER_DB as u8; + buf[1] = (VOLUME_STEPS_PER_DB >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + _ => return Some(InResponse::Rejected), + } + } + + fn endpoint_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!("Unhandled endpoint get request for endpoint {}.", endpoint_address); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL as u8 { + debug!( + "Unsupported endpoint get request for control selector {}.", + control_selector + ); + return Some(InResponse::Rejected); + } + + let sample_rate_hz = self.shared.sample_rate_hz.load(Ordering::Relaxed); + + buf[0] = (sample_rate_hz & 0xFF) as u8; + buf[1] = ((sample_rate_hz >> 8) & 0xFF) as u8; + buf[2] = ((sample_rate_hz >> 16) & 0xFF) as u8; + + Some(InResponse::Accepted(&buf[..3])) + } +} + +impl<'d> Handler for Control<'d> { + /// Called when the USB device has been enabled or disabled. + fn enabled(&mut self, enabled: bool) { + debug!("USB device enabled: {}", enabled); + } + + /// Called when the host has set the address of the device to `addr`. + fn addressed(&mut self, addr: u8) { + debug!("Host set address to: {}", addr); + } + + /// Called when the host has enabled or disabled the configuration of the device. + fn configured(&mut self, configured: bool) { + debug!("USB device configured: {}", configured); + } + + /// Called when remote wakeup feature is enabled or disabled. + fn remote_wakeup_enabled(&mut self, enabled: bool) { + debug!("USB remote wakeup enabled: {}", enabled); + } + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + debug!( + "USB set interface number {} to alt setting {}.", + iface, alternate_setting + ); + } + + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) { + let shared = self.shared; + shared.audio_settings.lock(|x| x.set(AudioSettings::default())); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + } + + /// Called when the bus has entered or exited the suspend state. + fn suspended(&mut self, suspended: bool) { + debug!("USB device suspended: {}", suspended); + } + + // Handle control set requests. + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_set_request(req, data), + Recipient::Endpoint => self.endpoint_set_request(req, data), + _ => Some(OutResponse::Rejected), + }, + _ => None, + } + } + + // Handle control get requests. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_get_request(req, buf), + Recipient::Endpoint => self.endpoint_get_request(req, buf), + _ => None, + }, + _ => None, + } + } +} diff --git a/embassy-usb/src/class/uac1/terminal_type.rs b/embassy-usb/src/class/uac1/terminal_type.rs new file mode 100644 index 000000000..65474a714 --- /dev/null +++ b/embassy-usb/src/class/uac1/terminal_type.rs @@ -0,0 +1,50 @@ +//! USB Audio Terminal Types from Universal Serial Bus Device Class Definition +//! for Terminal Types, Release 1.0 + +/// USB Audio Terminal Types from "Universal Serial Bus Device Class Definition +/// for Terminal Types, Release 1.0" +#[repr(u16)] +#[non_exhaustive] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum TerminalType { + // USB Terminal Types + UsbUndefined = 0x0100, + UsbStreaming = 0x0101, + UsbVendor = 0x01ff, + + // Input Terminal Types + InUndefined = 0x0200, + InMicrophone = 0x0201, + InDesktopMicrophone = 0x0202, + InPersonalMicrophone = 0x0203, + InOmniDirectionalMicrophone = 0x0204, + InMicrophoneArray = 0x0205, + InProcessingMicrophoneArray = 0x0206, + + // Output Terminal Types + OutUndefined = 0x0300, + OutSpeaker = 0x0301, + OutHeadphones = 0x0302, + OutHeadMountedDisplayAudio = 0x0303, + OutDesktopSpeaker = 0x0304, + OutRoomSpeaker = 0x0305, + OutCommunicationSpeaker = 0x0306, + OutLowFrequencyEffectsSpeaker = 0x0307, + + // External Terminal Types + ExtUndefined = 0x0600, + ExtAnalogConnector = 0x0601, + ExtDigitalAudioInterface = 0x0602, + ExtLineConnector = 0x0603, + ExtLegacyAudioConnector = 0x0604, + ExtSpdifConnector = 0x0605, + Ext1394DaStream = 0x0606, + Ext1394DvStreamSoundtrack = 0x0607, +} + +impl From for u16 { + fn from(t: TerminalType) -> u16 { + t as u16 + } +} diff --git a/embassy-usb/src/class/web_usb.rs b/embassy-usb/src/class/web_usb.rs index 10ebf318d..405944f14 100644 --- a/embassy-usb/src/class/web_usb.rs +++ b/embassy-usb/src/class/web_usb.rs @@ -140,7 +140,7 @@ pub struct WebUsb<'d, D: Driver<'d>> { impl<'d, D: Driver<'d>> WebUsb<'d, D> { /// Builder for the WebUSB capability implementation. /// - /// Pass in a USB `Builder`, a `State`, which holds the the control endpoint state, and a `Config` for the WebUSB configuration. + /// Pass in a USB `Builder`, a `State`, which holds the control endpoint state, and a `Config` for the WebUSB configuration. pub fn configure(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: &'d Config<'d>) { let mut func = builder.function(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); let mut iface = func.interface(); diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index eb3d1f53a..e9a6fd79a 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,4 +1,5 @@ //! Utilities for writing USB descriptors. +use embassy_usb_driver::EndpointType; use crate::builder::Config; use crate::driver::EndpointInfo; @@ -13,6 +14,8 @@ pub mod descriptor_type { pub const STRING: u8 = 3; pub const INTERFACE: u8 = 4; pub const ENDPOINT: u8 = 5; + pub const DEVICE_QUALIFIER: u8 = 6; + pub const OTHER_SPEED_CONFIGURATION: u8 = 7; pub const IAD: u8 = 11; pub const BOS: u8 = 15; pub const CAPABILITY: u8 = 16; @@ -36,6 +39,40 @@ pub mod capability_type { pub const PLATFORM: u8 = 5; } +/// USB endpoint synchronization type. The values of this enum can be directly +/// cast into `u8` to get the bmAttributes synchronization type bits. +/// Values other than `NoSynchronization` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SynchronizationType { + /// No synchronization is used. + NoSynchronization = 0b00, + /// Unsynchronized, although sinks provide data rate feedback. + Asynchronous = 0b01, + /// Synchronized using feedback or feedforward data rate information. + Adaptive = 0b10, + /// Synchronized to the USB’s SOF. + Synchronous = 0b11, +} + +/// USB endpoint usage type. The values of this enum can be directly cast into +/// `u8` to get the bmAttributes usage type bits. +/// Values other than `DataEndpoint` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsageType { + /// Use the endpoint for regular data transfer. + DataEndpoint = 0b00, + /// Endpoint conveys explicit feedback information for one or more data endpoints. + FeedbackEndpoint = 0b01, + /// A data endpoint that also serves as an implicit feedback endpoint for one or more data endpoints. + ImplicitFeedbackDataEndpoint = 0b10, + /// Reserved usage type. + Reserved = 0b11, +} + /// A writer for USB descriptors. pub(crate) struct DescriptorWriter<'a> { pub buf: &'a mut [u8], @@ -63,23 +100,26 @@ impl<'a> DescriptorWriter<'a> { self.position } - /// Writes an arbitrary (usually class-specific) descriptor. - pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) { - let length = descriptor.len(); + /// Writes an arbitrary (usually class-specific) descriptor with optional extra fields. + pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8], extra_fields: &[u8]) { + let descriptor_length = descriptor.len(); + let extra_fields_length = extra_fields.len(); + let total_length = descriptor_length + extra_fields_length; assert!( - (self.position + 2 + length) <= self.buf.len() && (length + 2) <= 255, + (self.position + 2 + total_length) <= self.buf.len() && (total_length + 2) <= 255, "Descriptor buffer full" ); - self.buf[self.position] = (length + 2) as u8; + self.buf[self.position] = (total_length + 2) as u8; self.buf[self.position + 1] = descriptor_type; let start = self.position + 2; - self.buf[start..start + length].copy_from_slice(descriptor); + self.buf[start..start + descriptor_length].copy_from_slice(descriptor); + self.buf[start + descriptor_length..start + total_length].copy_from_slice(extra_fields); - self.position = start + length; + self.position = start + total_length; } pub(crate) fn configuration(&mut self, config: &Config) { @@ -97,6 +137,7 @@ impl<'a> DescriptorWriter<'a> { | if config.supports_remote_wakeup { 0x20 } else { 0x00 }, // bmAttributes (config.max_power / 2) as u8, // bMaxPower ], + &[], ); } @@ -143,6 +184,7 @@ impl<'a> DescriptorWriter<'a> { function_protocol, 0, ], + &[], ); } @@ -193,6 +235,7 @@ impl<'a> DescriptorWriter<'a> { interface_protocol, // bInterfaceProtocol str_index, // iInterface ], + &[], ); } @@ -202,21 +245,50 @@ impl<'a> DescriptorWriter<'a> { /// /// * `endpoint` - Endpoint previously allocated with /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). - pub fn endpoint(&mut self, endpoint: &EndpointInfo) { + /// * `synchronization_type` - The synchronization type of the endpoint. + /// * `usage_type` - The usage type of the endpoint. + /// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor. + pub fn endpoint( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { match self.num_endpoints_mark { Some(mark) => self.buf[mark] += 1, None => panic!("you can only call `endpoint` after `interface/interface_alt`."), }; + let mut bm_attributes = endpoint.ep_type as u8; + + // Synchronization types other than `NoSynchronization`, + // and usage types other than `DataEndpoint` + // are only allowed for isochronous endpoints. + if endpoint.ep_type != EndpointType::Isochronous { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization); + assert_eq!(usage_type, UsageType::DataEndpoint); + } else { + if usage_type == UsageType::FeedbackEndpoint { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization) + } + + let synchronization_bm_attibutes: u8 = (synchronization_type as u8) << 2; + let usage_bm_attibutes: u8 = (usage_type as u8) << 4; + + bm_attributes |= usage_bm_attibutes | synchronization_bm_attibutes; + } + self.write( descriptor_type::ENDPOINT, &[ - endpoint.addr.into(), // bEndpointAddress - endpoint.ep_type as u8, // bmAttributes + endpoint.addr.into(), // bEndpointAddress + bm_attributes, // bmAttributes endpoint.max_packet_size as u8, (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize endpoint.interval_ms, // bInterval ], + extra_fields, ); } @@ -253,12 +325,12 @@ pub(crate) fn device_descriptor(config: &Config) -> [u8; 18] { [ 18, // bLength 0x01, // bDescriptorType - 0x10, - 0x02, // bcdUSB 2.1 - config.device_class, // bDeviceClass - config.device_sub_class, // bDeviceSubClass - config.device_protocol, // bDeviceProtocol - config.max_packet_size_0, // bMaxPacketSize0 + config.bcd_usb as u8, + (config.bcd_usb as u16 >> 8) as u8, // bcdUSB + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 config.vendor_id as u8, (config.vendor_id >> 8) as u8, // idVendor config.product_id as u8, @@ -272,6 +344,25 @@ pub(crate) fn device_descriptor(config: &Config) -> [u8; 18] { ] } +/// Create a new Device Qualifier Descriptor array. +/// +/// All device qualifier descriptors are always 10 bytes, so there's no need for +/// a variable-length buffer or DescriptorWriter. +pub(crate) fn device_qualifier_descriptor(config: &Config) -> [u8; 10] { + [ + 10, // bLength + 0x06, // bDescriptorType + config.bcd_usb as u8, + (config.bcd_usb as u16 >> 8) as u8, // bcdUSB + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 + 1, // bNumConfigurations + 0, // Reserved + ] +} + /// A writer for Binary Object Store descriptor. pub struct BosWriter<'a> { pub(crate) writer: DescriptorWriter<'a>, @@ -287,6 +378,9 @@ impl<'a> BosWriter<'a> { } pub(crate) fn bos(&mut self) { + if (self.writer.buf.len() - self.writer.position) < 5 { + return; + } self.num_caps_mark = Some(self.writer.position + 4); self.writer.write( descriptor_type::BOS, @@ -294,6 +388,7 @@ impl<'a> BosWriter<'a> { 0x00, 0x00, // wTotalLength 0x00, // bNumDeviceCaps ], + &[], ); self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]); @@ -329,6 +424,9 @@ impl<'a> BosWriter<'a> { } pub(crate) fn end_bos(&mut self) { + if self.writer.position == 0 { + return; + } self.num_caps_mark = None; let position = self.writer.position as u16; self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes()); diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index d58950838..0638fd0a2 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -23,7 +23,7 @@ mod config { use embassy_futures::select::{select, Either}; use heapless::Vec; -pub use crate::builder::{Builder, Config, FunctionBuilder, InterfaceAltBuilder, InterfaceBuilder}; +pub use crate::builder::{Builder, Config, FunctionBuilder, InterfaceAltBuilder, InterfaceBuilder, UsbVersion}; use crate::config::{MAX_HANDLER_COUNT, MAX_INTERFACE_COUNT}; use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; use crate::descriptor::{descriptor_type, lang_id}; @@ -120,7 +120,7 @@ pub trait Handler { /// # Returns /// /// If you didn't handle this request (for example if it's for the wrong interface), return - /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// `None`. In this case, the USB stack will continue calling the other handlers, to see /// if another handles it. /// /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack @@ -142,7 +142,7 @@ pub trait Handler { /// # Returns /// /// If you didn't handle this request (for example if it's for the wrong interface), return - /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// `None`. In this case, the USB stack will continue calling the other handlers, to see /// if another handles it. /// /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack @@ -190,6 +190,7 @@ struct Inner<'d, D: Driver<'d>> { config: Config<'d>, device_descriptor: [u8; 18], + device_qualifier_descriptor: [u8; 10], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, @@ -225,6 +226,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { // This prevent further allocation by consuming the driver. let (bus, control) = driver.start(config.max_packet_size_0 as u16); let device_descriptor = descriptor::device_descriptor(&config); + let device_qualifier_descriptor = descriptor::device_qualifier_descriptor(&config); Self { control_buf, @@ -233,6 +235,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { bus, config, device_descriptor, + device_qualifier_descriptor, config_descriptor, bos_descriptor, msos_descriptor, @@ -764,6 +767,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } } } + descriptor_type::DEVICE_QUALIFIER => InResponse::Accepted(&self.device_qualifier_descriptor), _ => InResponse::Rejected, } } diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs index cb9fe2576..ea6347c7d 100644 --- a/embassy-usb/src/types.rs +++ b/embassy-usb/src/types.rs @@ -18,6 +18,12 @@ impl From for u8 { } } +impl core::fmt::Display for InterfaceNumber { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + /// A handle for a USB string descriptor that contains its index. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index 93e49faef..4d633e8a8 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -5,16 +5,16 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } -embassy-nrf = { version = "0.2.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } -embassy-boot = { version = "0.3.0", path = "../../../../embassy-boot", features = [] } -embassy-boot-nrf = { version = "0.3.0", path = "../../../../embassy-boot-nrf", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [] } +embassy-nrf = { version = "0.3.1", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } +embassy-boot = { version = "0.4.0", path = "../../../../embassy-boot", features = [] } +embassy-boot-nrf = { version = "0.4.0", path = "../../../../embassy-boot-nrf", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/nrf/build.rs b/examples/boot/application/nrf/build.rs index cd1a264c4..e1da69328 100644 --- a/examples/boot/application/nrf/build.rs +++ b/examples/boot/application/nrf/build.rs @@ -31,4 +31,7 @@ fn main() { 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/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 851a3d721..2c1d1a7bb 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -2,6 +2,9 @@ #![no_main] #![macro_use] +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot::State; use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; @@ -22,6 +25,7 @@ async fn main(_spawner: Spawner) { let mut button = Input::new(p.P0_11, Pull::Up); let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + let mut led_reverted = Output::new(p.P0_14, Level::High, OutputDrive::Standard); //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); //let mut button = Input::new(p.P1_02, Pull::Up); @@ -53,6 +57,13 @@ async fn main(_spawner: Spawner) { let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc); let mut magic = [0; 4]; let mut updater = FirmwareUpdater::new(config, &mut magic); + let state = updater.get_state().await.unwrap(); + if state == State::Revert { + led_reverted.set_low(); + } else { + led_reverted.set_high(); + } + loop { led.set_low(); button.wait_for_any_edge().await; diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index 8bb8afdfe..be283fb27 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -5,16 +5,16 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } -embassy-rp = { version = "0.2.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } -embassy-boot-rp = { version = "0.3.0", path = "../../../../embassy-boot-rp", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [] } +embassy-rp = { version = "0.4.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } +embassy-boot-rp = { version = "0.5.0", path = "../../../../embassy-boot-rp", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = "0.3" -defmt-rtt = "0.4" -panic-probe = { version = "0.3", features = ["print-defmt"], optional = true } +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"], optional = true } panic-reset = { version = "0.1.1", optional = true } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index 1c2934298..b3466e288 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -5,15 +5,15 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32" } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32" } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index 09e34c7df..72dbded5f 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -5,15 +5,15 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti", "single-bank"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } embedded-storage = "0.3.1" diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index 5e7f4d5e7..57fb8312c 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -5,15 +5,15 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } embedded-storage = "0.3.1" diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index 60fdcfafb..7dbbba138 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -5,15 +5,15 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index fe3ab2c04..9549b2048 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -5,15 +5,15 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index 169856358..03daeb0bc 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -5,15 +5,15 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 7cef8fe0d..e582628aa 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -5,17 +5,17 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } -embassy-usb = { version = "0.3.0", path = "../../../../embassy-usb" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } +embassy-usb = { version = "0.4.0", path = "../../../../embassy-usb" } embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32wb-dfu/memory.x b/examples/boot/application/stm32wb-dfu/memory.x index ff1b800d2..f1e6b053c 100644 --- a/examples/boot/application/stm32wb-dfu/memory.x +++ b/examples/boot/application/stm32wb-dfu/memory.x @@ -1,10 +1,10 @@ MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ - BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K - BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - FLASH : ORIGIN = 0x08008000, LENGTH = 128K - DFU : ORIGIN = 0x08028000, LENGTH = 132K + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 48K + BOOTLOADER_STATE : ORIGIN = 0x0800C000, LENGTH = 4K + FLASH : ORIGIN = 0x0800D000, LENGTH = 120K + DFU : ORIGIN = 0x0802B000, LENGTH = 120K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } diff --git a/examples/boot/application/stm32wb-dfu/secrets/key.sec b/examples/boot/application/stm32wb-dfu/secrets/key.sec new file mode 100644 index 000000000..52e7f125b --- /dev/null +++ b/examples/boot/application/stm32wb-dfu/secrets/key.sec @@ -0,0 +1,2 @@ +untrusted comment: signify secret key +RWRCSwAAAAATdHQF3B4jEIoNZrjADRp2LbjJjNdNNzKwTCe4IB6mDNq96pe53nbNxwbdCc/T4hrz7W+Kx1MwrZ0Yz5xebSK5Z0Kh/3Cdf039U5f+eoTDS2fIGbohyUbrtwKzjyE0qXI= diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs index 0ab99ff90..5e7b71f5a 100644 --- a/examples/boot/application/stm32wb-dfu/src/main.rs +++ b/examples/boot/application/stm32wb-dfu/src/main.rs @@ -13,7 +13,7 @@ use embassy_stm32::usb::{self, Driver}; use embassy_stm32::{bind_interrupts, peripherals}; use embassy_sync::blocking_mutex::Mutex; use embassy_time::Duration; -use embassy_usb::Builder; +use embassy_usb::{msos, Builder}; use embassy_usb_dfu::consts::DfuAttributes; use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; use panic_reset as _; @@ -22,6 +22,11 @@ bind_interrupts!(struct Irqs { USB_LP => usb::InterruptHandler; }); +// This is a randomly generated GUID to allow clients on Windows to find your device. +// +// N.B. update to a custom GUID for your own device! +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"]; + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = embassy_stm32::Config::default(); @@ -44,7 +49,7 @@ async fn main(_spawner: Spawner) { let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 64]; - let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD); + let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD, ResetImmediate); let mut builder = Builder::new( driver, config, @@ -54,7 +59,28 @@ async fn main(_spawner: Spawner) { &mut control_buf, ); - usb_dfu::<_, _, ResetImmediate>(&mut builder, &mut state, Duration::from_millis(2500)); + // We add MSOS headers so that the device automatically gets assigned the WinUSB driver on Windows. + // Otherwise users need to do this manually using a tool like Zadig. + // + // It seems these always need to be at added at the device level for this to work and for + // composite devices they also need to be added on the function level (as shown later). + // + builder.msos_descriptor(msos::windows_version::WIN8_1, 2); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + usb_dfu(&mut builder, &mut state, Duration::from_millis(2500), |func| { + // You likely don't have to add these function level headers if your USB device is not composite + // (i.e. if your device does not expose another interface in addition to DFU) + func.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + func.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + }); let mut dev = builder.build(); dev.run().await diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index 860a835a9..3ed04b472 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -5,15 +5,15 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.3.0", path = "../../../../embassy-boot-stm32", features = [] } +embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } diff --git a/examples/boot/application/stm32wl/memory.x b/examples/boot/application/stm32wl/memory.x index 5af1723f5..20109e37e 100644 --- a/examples/boot/application/stm32wl/memory.x +++ b/examples/boot/application/stm32wl/memory.x @@ -5,8 +5,8 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K FLASH : ORIGIN = 0x08008000, LENGTH = 64K DFU : ORIGIN = 0x08018000, LENGTH = 68K - SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64 - RAM (rwx) : ORIGIN = 0x20000040, LENGTH = 32K - 64 + SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128 + RAM (rwx) : ORIGIN = 0x20000080, LENGTH = 32K - 128 } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); @@ -21,4 +21,4 @@ SECTIONS { *(.shared_data) } > SHARED_RAM -} \ No newline at end of file +} diff --git a/examples/boot/application/stm32wl/src/bin/a.rs b/examples/boot/application/stm32wl/src/bin/a.rs index 127de0237..e4526927f 100644 --- a/examples/boot/application/stm32wl/src/bin/a.rs +++ b/examples/boot/application/stm32wl/src/bin/a.rs @@ -20,7 +20,7 @@ static APP_B: &[u8] = &[0, 1, 2, 3]; #[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/boot/application/stm32wl/src/bin/b.rs b/examples/boot/application/stm32wl/src/bin/b.rs index 768dadf8b..6016a9555 100644 --- a/examples/boot/application/stm32wl/src/bin/b.rs +++ b/examples/boot/application/stm32wl/src/bin/b.rs @@ -11,7 +11,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use panic_reset as _; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index 9d5d51a13..897890ca4 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -6,13 +6,13 @@ description = "Bootloader for nRF chips" license = "MIT OR Apache-2.0" [dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } embassy-nrf = { path = "../../../../embassy-nrf", features = [] } embassy-boot-nrf = { path = "../../../../embassy-boot-nrf" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } cfg-if = "1.0.0" diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs index 67c700437..b849a0df3 100644 --- a/examples/boot/bootloader/nrf/src/main.rs +++ b/examples/boot/bootloader/nrf/src/main.rs @@ -8,7 +8,7 @@ use cortex_m_rt::{entry, exception}; use defmt_rtt as _; use embassy_boot_nrf::*; use embassy_nrf::nvmc::Nvmc; -use embassy_nrf::wdt; +use embassy_nrf::wdt::{self, HaltConfig, SleepConfig}; use embassy_sync::blocking_mutex::Mutex; #[entry] @@ -25,8 +25,8 @@ fn main() -> ! { let mut wdt_config = wdt::Config::default(); wdt_config.timeout_ticks = 32768 * 5; // timeout seconds - wdt_config.run_during_sleep = true; - wdt_config.run_during_debug_halt = false; + wdt_config.action_during_sleep = SleepConfig::RUN; + wdt_config.action_during_debug_halt = HaltConfig::PAUSE; let flash = WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config); let flash = Mutex::new(RefCell::new(flash)); diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index 9df396e5e..090a581d4 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -6,12 +6,12 @@ description = "Example bootloader for RP2040 chips" license = "MIT OR Apache-2.0" [dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } embassy-rp = { path = "../../../../embassy-rp", features = ["rp2040"] } embassy-boot-rp = { path = "../../../../embassy-boot-rp" } -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } embassy-time = { path = "../../../../embassy-time", features = [] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } diff --git a/examples/boot/bootloader/stm32-dual-bank/Cargo.toml b/examples/boot/bootloader/stm32-dual-bank/Cargo.toml index b91b05412..67edc6a6c 100644 --- a/examples/boot/bootloader/stm32-dual-bank/Cargo.toml +++ b/examples/boot/bootloader/stm32-dual-bank/Cargo.toml @@ -6,8 +6,8 @@ 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 } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", optional = true } embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } @@ -15,7 +15,7 @@ cortex-m = { version = "0.7.6", features = [ "inline-asm", "critical-section-single-core", ] } -embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" embedded-storage-async = "0.4.0" diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index 541186949..fe81b5151 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -6,13 +6,13 @@ description = "Example bootloader for STM32 chips" license = "MIT OR Apache-2.0" [dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", 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.6.0", path = "../../../../embassy-sync" } +embassy-sync = { version = "0.7.0", path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" embedded-storage-async = "0.4.0" diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index 050b672ce..0bb93b12e 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -6,19 +6,19 @@ description = "Example USB DFUbootloader for the STM32WB series of chips" license = "MIT OR Apache-2.0" [dependencies] -defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.4", optional = true } +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.0.0", 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.6.0", path = "../../../../embassy-sync" } +embassy-sync = { version = "0.7.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" embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["dfu", "cortex-m"] } -embassy-usb = { version = "0.3.0", path = "../../../../embassy-usb", default-features = false } +embassy-usb = { version = "0.4.0", path = "../../../../embassy-usb", default-features = false } embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" } [features] @@ -30,6 +30,7 @@ defmt = [ "embassy-usb/defmt", "embassy-usb-dfu/defmt" ] +verify = ["embassy-usb-dfu/ed25519-salty"] [profile.dev] debug = 2 diff --git a/examples/boot/bootloader/stm32wb-dfu/README.md b/examples/boot/bootloader/stm32wb-dfu/README.md index d5c6ea57c..99a7002c4 100644 --- a/examples/boot/bootloader/stm32wb-dfu/README.md +++ b/examples/boot/bootloader/stm32wb-dfu/README.md @@ -1,11 +1,63 @@ # Bootloader for STM32 -The bootloader uses `embassy-boot` to interact with the flash. +This bootloader implementation uses `embassy-boot` and `embassy-usb-dfu` to manage firmware updates and interact with the flash memory on STM32WB55 devices. -# Usage +## Prerequisites -Flash the bootloader +- Rust toolchain with `cargo` installed +- `cargo-flash` for flashing the bootloader +- `dfu-util` for firmware updates +- `cargo-binutils` for binary generation + +## Usage + +### 1. Flash the Bootloader + +First, flash the bootloader to your device: ``` cargo flash --features embassy-stm32/stm32wb55rg --release --chip STM32WB55RGVx ``` + +### 2. Build and Flash Application + +Generate your application binary and flash it using DFU: + +``` +cargo objcopy --release -- -O binary fw.bin +dfu-util -d c0de:cafe -w -D fw.bin +``` + +### 3. Sign Updates Before Flashing (Optional) + +Currently, embassy-usb-dfu only supports a limited implementation of the generic support for ed25519-based update verfication in embassy-boot. This implementation assumes that a signature is simply concatenated to the end of an update binary. For more details, please see https://embassy.dev/book/#_verification and/or refer to the documentation for embassy-boot-dfu. + +To sign (and then verify) application updates, you will first need to generate a key pair: + +``` +signify-openbsd -G -n -p secrets/key.pub -s secrets/key.sec +tail -n1 secrets/key.pub | base64 -d -i - | dd ibs=10 skip=1 > secrets/key.pub.short +``` + +Then you will need to sign all you binaries with the private key: + +``` +cargo objcopy --release -- -O binary fw.bin +shasum -a 512 -b fw.bin | head -c128 | xxd -p -r > target/fw-hash.txt +signify-openbsd -S -s secrets/key.sec -m target/fw-hash.txt -x target/fw-hash.sig +cp fw.bin fw-signed.bin +tail -n1 target/fw-hash.sig | base64 -d -i - | dd ibs=10 skip=1 >> fw-signed.bin +dfu-util -d c0de:cafe -w -D fw-signed.bin +``` + +Finally, as shown in this example with the `verify` feature flag enabled, you then need to embed the public key into your bootloader so that it can verify update signatures. + +N.B. Please note that the exact steps above are NOT a good example of how to manage your keys securely. In a production environment, you should take great care to ensure that (at least the private key) is protected and not leaked into your version control system. + +## Troubleshooting + +- Make sure your device is in DFU mode before flashing +- Verify the USB VID:PID matches your device (c0de:cafe) +- Check USB connections if the device is not detected +- Make sure the transfer size option of `dfu-util` matches the bootloader configuration. By default, `dfu-util` will use the transfer size reported by the device, but you can override it with the `-t` option if needed. +- Make sure `control_buf` size is larger than or equal to the `usb_dfu` `BLOCK_SIZE` parameter (in this example, both are set to 4096 bytes). diff --git a/examples/boot/bootloader/stm32wb-dfu/memory.x b/examples/boot/bootloader/stm32wb-dfu/memory.x index 858062631..77c4d2ee2 100644 --- a/examples/boot/bootloader/stm32wb-dfu/memory.x +++ b/examples/boot/bootloader/stm32wb-dfu/memory.x @@ -1,10 +1,10 @@ MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ - FLASH : ORIGIN = 0x08000000, LENGTH = 24K - BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - ACTIVE : ORIGIN = 0x08008000, LENGTH = 128K - DFU : ORIGIN = 0x08028000, LENGTH = 132K + FLASH : ORIGIN = 0x08000000, LENGTH = 48K + BOOTLOADER_STATE : ORIGIN = 0x0800C000, LENGTH = 4K + ACTIVE : ORIGIN = 0x0800D000, LENGTH = 120K + DFU : ORIGIN = 0x0802B000, LENGTH = 120K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K } diff --git a/examples/boot/bootloader/stm32wb-dfu/secrets/key.pub.short b/examples/boot/bootloader/stm32wb-dfu/secrets/key.pub.short new file mode 100644 index 000000000..7a4de8585 --- /dev/null +++ b/examples/boot/bootloader/stm32wb-dfu/secrets/key.pub.short @@ -0,0 +1 @@ +gBpMSzKg!F!4r \ No newline at end of file diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs index 093b39f9d..107f243fd 100644 --- a/examples/boot/bootloader/stm32wb-dfu/src/main.rs +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -12,7 +12,7 @@ use embassy_stm32::rcc::WPAN_DEFAULT; use embassy_stm32::usb::Driver; use embassy_stm32::{bind_interrupts, peripherals, usb}; use embassy_sync::blocking_mutex::Mutex; -use embassy_usb::Builder; +use embassy_usb::{msos, Builder}; use embassy_usb_dfu::consts::DfuAttributes; use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; @@ -20,6 +20,17 @@ bind_interrupts!(struct Irqs { USB_LP => usb::InterruptHandler; }); +// This is a randomly generated GUID to allow clients on Windows to find your device. +// +// N.B. update to a custom GUID for your own device! +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"]; + +// This is a randomly generated example key. +// +// N.B. Please replace with your own! +#[cfg(feature = "verify")] +static PUBLIC_SIGNING_KEY: &[u8; 32] = include_bytes!("../secrets/key.pub.short"); + #[entry] fn main() -> ! { let mut config = embassy_stm32::Config::default(); @@ -52,7 +63,13 @@ fn main() -> ! { let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 4096]; - let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD); + + #[cfg(not(feature = "verify"))] + let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate); + + #[cfg(feature = "verify")] + let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate, PUBLIC_SIGNING_KEY); + let mut builder = Builder::new( driver, config, @@ -62,7 +79,28 @@ fn main() -> ! { &mut control_buf, ); - usb_dfu::<_, _, _, ResetImmediate, 4096>(&mut builder, &mut state); + // We add MSOS headers so that the device automatically gets assigned the WinUSB driver on Windows. + // Otherwise users need to do this manually using a tool like Zadig. + // + // It seems these always need to be at added at the device level for this to work and for + // composite devices they also need to be added on the function level (as shown later). + // + builder.msos_descriptor(msos::windows_version::WIN8_1, 2); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + usb_dfu::<_, _, _, _, 4096>(&mut builder, &mut state, |func| { + // You likely don't have to add these function level headers if your USB device is not composite + // (i.e. if your device does not expose another interface in addition to DFU) + func.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + func.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + }); let mut dev = builder.build(); embassy_futures::block_on(dev.run()); diff --git a/examples/rp23/.cargo/config.toml b/examples/lpc55s69/.cargo/config.toml similarity index 67% rename from examples/rp23/.cargo/config.toml rename to examples/lpc55s69/.cargo/config.toml index f77e004dc..9556de72f 100644 --- a/examples/rp23/.cargo/config.toml +++ b/examples/lpc55s69/.cargo/config.toml @@ -1,6 +1,5 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -#runner = "probe-rs run --chip RP2040" -runner = "elf2uf2-rs -d" +runner = "probe-rs run --chip LPC55S69JBD100" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/lpc55s69/Cargo.toml b/examples/lpc55s69/Cargo.toml new file mode 100644 index 000000000..7f81e9c7f --- /dev/null +++ b/examples/lpc55s69/Cargo.toml @@ -0,0 +1,22 @@ +[package] +edition = "2021" +name = "embassy-nxp-lpc55s69-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-nxp = { version = "0.1.0", path = "../../embassy-nxp", features = ["rt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "1.0.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7.0"} +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/examples/rp23/build.rs b/examples/lpc55s69/build.rs similarity index 100% rename from examples/rp23/build.rs rename to examples/lpc55s69/build.rs diff --git a/examples/lpc55s69/memory.x b/examples/lpc55s69/memory.x new file mode 100644 index 000000000..1483b2fad --- /dev/null +++ b/examples/lpc55s69/memory.x @@ -0,0 +1,28 @@ +/* File originally from lpc55-hal repo: https://github.com/lpc55/lpc55-hal/blob/main/memory.x */ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 512K + + /* for use with standard link.x */ + RAM : ORIGIN = 0x20000000, LENGTH = 256K + + /* would be used with proper link.x */ + /* needs changes to r0 (initialization code) */ + /* SRAM0 : ORIGIN = 0x20000000, LENGTH = 64K */ + /* SRAM1 : ORIGIN = 0x20010000, LENGTH = 64K */ + /* SRAM2 : ORIGIN = 0x20020000, LENGTH = 64K */ + /* SRAM3 : ORIGIN = 0x20030000, LENGTH = 64K */ + + /* CASPER SRAM regions */ + /* SRAMX0: ORIGIN = 0x1400_0000, LENGTH = 4K /1* to 0x1400_0FFF *1/ */ + /* SRAMX1: ORIGIN = 0x1400_4000, LENGTH = 4K /1* to 0x1400_4FFF *1/ */ + + /* USB1 SRAM regin */ + /* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */ + + /* To define our own USB RAM section in one regular */ + /* RAM, probably easiest to shorten length of RAM */ + /* above, and use this freed RAM section */ + +} + diff --git a/examples/lpc55s69/src/bin/blinky_nop.rs b/examples/lpc55s69/src/bin/blinky_nop.rs new file mode 100644 index 000000000..58e2d9808 --- /dev/null +++ b/examples/lpc55s69/src/bin/blinky_nop.rs @@ -0,0 +1,33 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on PIO1_6. + +#![no_std] +#![no_main] + +use cortex_m::asm::nop; +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Level, Output}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + + loop { + info!("led off!"); + led.set_high(); + + for _ in 0..200_000 { + nop(); + } + + info!("led on!"); + led.set_low(); + + for _ in 0..200_000 { + nop(); + } + } +} diff --git a/examples/lpc55s69/src/bin/button_executor.rs b/examples/lpc55s69/src/bin/button_executor.rs new file mode 100644 index 000000000..836b1c9eb --- /dev/null +++ b/examples/lpc55s69/src/bin/button_executor.rs @@ -0,0 +1,25 @@ +//! This example has been made with the LPCXpresso55S69 board in mind, which has a built-in LED on +//! PIO1_6 and a button (labeled "user") on PIO1_9. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nxp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_nxp::init(Default::default()); + + let mut led = Output::new(p.PIO1_6, Level::Low); + let mut button = Input::new(p.PIO1_9, Pull::Up); + + info!("Entered main loop"); + loop { + button.wait_for_rising_edge().await; + info!("Button pressed"); + led.toggle(); + } +} diff --git a/examples/mimxrt6/.cargo/config.toml b/examples/mimxrt6/.cargo/config.toml new file mode 100644 index 000000000..db42be81d --- /dev/null +++ b/examples/mimxrt6/.cargo/config.toml @@ -0,0 +1,17 @@ +[target.thumbv8m.main-none-eabihf] +runner = 'probe-rs run --chip MIMXRT685SFVKB' + +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", +] + +[build] +target = "thumbv8m.main-none-eabihf" # Cortex-M33 + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mimxrt6/.gitignore b/examples/mimxrt6/.gitignore new file mode 100644 index 000000000..418e01907 --- /dev/null +++ b/examples/mimxrt6/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +/debug +/target + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/examples/mimxrt6/Cargo.toml b/examples/mimxrt6/Cargo.toml new file mode 100644 index 000000000..65cb9e3ca --- /dev/null +++ b/examples/mimxrt6/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "embassy-imxrt-examples" +version = "0.1.0" +edition = "2021" +license = "MIT or Apache-2.0" + +[dependencies] +cortex-m = { version = "0.7.7", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.3" +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } +embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-os-timer"] } +embassy-time = { version = "0.4", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0.0" + +mimxrt600-fcb = "0.2.2" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/mimxrt6/README.md b/examples/mimxrt6/README.md new file mode 100644 index 000000000..6d5031cf9 --- /dev/null +++ b/examples/mimxrt6/README.md @@ -0,0 +1,18 @@ +# embassy-imxrt-examples + +## Introduction + +These examples illustrates how to use the embassy-imxrt HAL. + +## Adding Examples +Add uniquely named example to `src/bin` like `adc.rs` + +## Build +`cd` to examples folder +`cargo build --bin ` for example, `cargo build --bin adc` + +## Run +Assuming RT685 is powered and connected to Jlink debug probe and the latest probe-rs is installed via + `$ cargo install probe-rs-tools --git https://github.com/probe-rs/probe-rs --locked` +`cd` to examples folder +`cargo run --bin ` for example, `cargo run --bin adc` \ No newline at end of file diff --git a/examples/mimxrt6/build.rs b/examples/mimxrt6/build.rs new file mode 100644 index 000000000..56010dfd6 --- /dev/null +++ b/examples/mimxrt6/build.rs @@ -0,0 +1,45 @@ +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"); + + // Inject crate version into the .biv section. + File::create(out.join("biv.rs")) + .unwrap() + .write_all( + format!( + r##" +#[unsafe(link_section = ".biv")] +#[used] +static BOOT_IMAGE_VERSION: u32 = 0x{:02x}{:02x}{:02x}00; +"##, + env!("CARGO_PKG_VERSION_MAJOR") + .parse::() + .expect("should have major version"), + env!("CARGO_PKG_VERSION_MINOR") + .parse::() + .expect("should have minor version"), + env!("CARGO_PKG_VERSION_PATCH") + .parse::() + .expect("should have patch version"), + ) + .as_bytes(), + ) + .unwrap(); +} diff --git a/examples/mimxrt6/memory.x b/examples/mimxrt6/memory.x new file mode 100644 index 000000000..6c4410f57 --- /dev/null +++ b/examples/mimxrt6/memory.x @@ -0,0 +1,34 @@ +MEMORY { + OTFAD : ORIGIN = 0x08000000, LENGTH = 256 + FCB : ORIGIN = 0x08000400, LENGTH = 512 + BIV : ORIGIN = 0x08000600, LENGTH = 4 + KEYSTORE : ORIGIN = 0x08000800, LENGTH = 2K + FLASH : ORIGIN = 0x08001000, LENGTH = 1M + RAM : ORIGIN = 0x20080000, LENGTH = 1536K +} + +SECTIONS { + .otfad : { + . = ALIGN(4); + KEEP(* (.otfad)) + . = ALIGN(4); + } > OTFAD + + .fcb : { + . = ALIGN(4); + KEEP(* (.fcb)) + . = ALIGN(4); + } > FCB + + .biv : { + . = ALIGN(4); + KEEP(* (.biv)) + . = ALIGN(4); + } > BIV + + .keystore : { + . = ALIGN(4); + KEEP(* (.keystore)) + . = ALIGN(4); + } > KEYSTORE +} diff --git a/examples/mimxrt6/src/bin/blinky.rs b/examples/mimxrt6/src/bin/blinky.rs new file mode 100644 index 000000000..de079d505 --- /dev/null +++ b/examples/mimxrt6/src/bin/blinky.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_imxrt::gpio; +use embassy_time::Timer; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!("Initializing GPIO"); + + let mut led = gpio::Output::new( + p.PIO0_26, + gpio::Level::Low, + gpio::DriveMode::PushPull, + gpio::DriveStrength::Normal, + gpio::SlewRate::Standard, + ); + + loop { + info!("Toggling LED"); + led.toggle(); + Timer::after_secs(1).await; + } +} diff --git a/examples/mimxrt6/src/bin/button.rs b/examples/mimxrt6/src/bin/button.rs new file mode 100644 index 000000000..efb7f14af --- /dev/null +++ b/examples/mimxrt6/src/bin/button.rs @@ -0,0 +1,29 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_imxrt::gpio; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + let mut user1 = gpio::Input::new(p.PIO1_1, gpio::Pull::None, gpio::Inverter::Disabled); + let mut user2 = gpio::Input::new(p.PIO0_10, gpio::Pull::None, gpio::Inverter::Disabled); + + loop { + let res = select(user1.wait_for_falling_edge(), user2.wait_for_falling_edge()).await; + + match res { + Either::First(()) => { + info!("Button `USER1' pressed"); + } + Either::Second(()) => { + info!("Button `USER2' pressed"); + } + } + } +} diff --git a/examples/mimxrt6/src/bin/crc.rs b/examples/mimxrt6/src/bin/crc.rs new file mode 100644 index 000000000..005a250e5 --- /dev/null +++ b/examples/mimxrt6/src/bin/crc.rs @@ -0,0 +1,175 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_imxrt::crc::{Config, Crc, Polynomial}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_imxrt::init(Default::default()); + let data = b"123456789"; + + info!("Initializing CRC"); + + // CRC-CCITT + let mut crc = Crc::new(p.CRC.reborrow(), Default::default()); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x29b1); + + // CRC16-ARC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xbb3d); + + // CRC16-CMS + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xaee7); + + // CRC16-DDS-110 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0x800d, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x9ecf); + + // CRC16-MAXIM-DOW + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: true, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x44c2); + + // CRC16-MODBUS + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0xffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x4b37); + + // CRC32-BZIP2 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: true, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xfc89_1918); + + // CRC32-CKSUM + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: true, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x765e_7680); + + // CRC32-ISO-HDLC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: true, + reverse_out: true, + complement_out: true, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xcbf4_3926); + + // CRC32-JAMCRC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x340b_c6d9); + + // CRC32-MPEG-2 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x0376_e6e7); + + info!("end program"); + cortex_m::asm::bkpt(); +} diff --git a/examples/mimxrt6/src/bin/hello.rs b/examples/mimxrt6/src/bin/hello.rs new file mode 100644 index 000000000..c640241ce --- /dev/null +++ b/examples/mimxrt6/src/bin/hello.rs @@ -0,0 +1,17 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::info; +use embassy_executor::Spawner; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let _p = embassy_imxrt::init(Default::default()); + loop { + info!("Hello"); + cortex_m::asm::delay(5_000_000); + } +} diff --git a/examples/mimxrt6/src/bin/rng.rs b/examples/mimxrt6/src/bin/rng.rs new file mode 100644 index 000000000..9468dd109 --- /dev/null +++ b/examples/mimxrt6/src/bin/rng.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_imxrt::rng::Rng; +use embassy_imxrt::{bind_interrupts, peripherals, rng}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!("Initializing RNG"); + let mut rng = Rng::new(p.RNG, Irqs); + let mut buf = [0u8; 65]; + + // Async interface + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); + + // RngCore interface + let mut random_bytes = [0; 16]; + + let random_u32 = rng.blocking_next_u32(); + let random_u64 = rng.blocking_next_u64(); + + rng.blocking_fill_bytes(&mut random_bytes); + + info!("random_u32 {}", random_u32); + info!("random_u64 {}", random_u64); + info!("random_bytes {}", random_bytes); +} diff --git a/examples/mimxrt6/src/bin/uart-async.rs b/examples/mimxrt6/src/bin/uart-async.rs new file mode 100644 index 000000000..58e31f379 --- /dev/null +++ b/examples/mimxrt6/src/bin/uart-async.rs @@ -0,0 +1,87 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_imxrt::flexcomm::uart::{self, Async, Uart}; +use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FLEXCOMM2 => uart::InterruptHandler; + FLEXCOMM4 => uart::InterruptHandler; +}); + +const BUFLEN: usize = 16; + +#[embassy_executor::task] +async fn usart4_task(mut uart: Uart<'static, Async>) { + info!("RX Task"); + + loop { + let mut rx_buf = [0; BUFLEN]; + uart.read(&mut rx_buf).await.unwrap(); + info!("usart4: rx_buf {:02x}", rx_buf); + + Timer::after_millis(10).await; + + let tx_buf = [0xaa; BUFLEN]; + uart.write(&tx_buf).await.unwrap(); + info!("usart4: tx_buf {:02x}", tx_buf); + } +} + +#[embassy_executor::task] +async fn usart2_task(mut uart: Uart<'static, Async>) { + info!("TX Task"); + + loop { + let tx_buf = [0x55; BUFLEN]; + uart.write(&tx_buf).await.unwrap(); + info!("usart2: tx_buf {:02x}", tx_buf); + + Timer::after_millis(10).await; + + let mut rx_buf = [0x00; BUFLEN]; + uart.read(&mut rx_buf).await.unwrap(); + info!("usart2: rx_buf {:02x}", rx_buf); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!("UART test start"); + + let usart4 = Uart::new_with_rtscts( + p.FLEXCOMM4, + p.PIO0_29, + p.PIO0_30, + p.PIO1_0, + p.PIO0_31, + Irqs, + p.DMA0_CH9, + p.DMA0_CH8, + Default::default(), + ) + .unwrap(); + spawner.must_spawn(usart4_task(usart4)); + + let usart2 = Uart::new_with_rtscts( + p.FLEXCOMM2, + p.PIO0_15, + p.PIO0_16, + p.PIO0_18, + p.PIO0_17, + Irqs, + p.DMA0_CH5, + p.DMA0_CH4, + Default::default(), + ) + .unwrap(); + spawner.must_spawn(usart2_task(usart2)); +} diff --git a/examples/mimxrt6/src/bin/uart.rs b/examples/mimxrt6/src/bin/uart.rs new file mode 100644 index 000000000..d6a75f85d --- /dev/null +++ b/examples/mimxrt6/src/bin/uart.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_imxrt::flexcomm::uart::{Blocking, Uart, UartRx, UartTx}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn usart4_task(mut uart: UartRx<'static, Blocking>) { + info!("RX Task"); + + loop { + let mut buf = [0; 8]; + + Timer::after_millis(10).await; + + uart.blocking_read(&mut buf).unwrap(); + + let s = core::str::from_utf8(&buf).unwrap(); + + info!("Received '{}'", s); + } +} + +#[embassy_executor::task] +async fn usart2_task(mut uart: UartTx<'static, Blocking>) { + info!("TX Task"); + + loop { + let buf = "Testing\0".as_bytes(); + + uart.blocking_write(buf).unwrap(); + + Timer::after_millis(10).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!("UART test start"); + + let usart4 = Uart::new_blocking(p.FLEXCOMM4, p.PIO0_29, p.PIO0_30, Default::default()).unwrap(); + + let (_, usart4) = usart4.split(); + spawner.must_spawn(usart4_task(usart4)); + + let usart2 = UartTx::new_blocking(p.FLEXCOMM2, p.PIO0_15, Default::default()).unwrap(); + spawner.must_spawn(usart2_task(usart2)); +} diff --git a/examples/mimxrt6/src/lib.rs b/examples/mimxrt6/src/lib.rs new file mode 100644 index 000000000..3c3ea1981 --- /dev/null +++ b/examples/mimxrt6/src/lib.rs @@ -0,0 +1,20 @@ +#![no_std] + +use mimxrt600_fcb::FlexSPIFlashConfigurationBlock; +use {defmt_rtt as _, panic_probe as _}; + +// auto-generated version information from Cargo.toml +include!(concat!(env!("OUT_DIR"), "/biv.rs")); + +#[unsafe(link_section = ".otfad")] +#[used] +static OTFAD: [u8; 256] = [0; 256]; + +#[rustfmt::skip] +#[unsafe(link_section = ".fcb")] +#[used] +static FCB: FlexSPIFlashConfigurationBlock = FlexSPIFlashConfigurationBlock::build(); + +#[unsafe(link_section = ".keystore")] +#[used] +static KEYSTORE: [u8; 2048] = [0; 2048]; diff --git a/examples/mspm0c1104/.cargo/config.toml b/examples/mspm0c1104/.cargo/config.toml new file mode 100644 index 000000000..204a56b1c --- /dev/null +++ b/examples/mspm0c1104/.cargo/config.toml @@ -0,0 +1,11 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace MSPM0C1104 with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip MSPM0C1104 --protocol=swd" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "debug" +# defmt's buffer needs to be shrunk since the MSPM0C1104 only has 1KB of ram. +DEFMT_RTT_BUFFER_SIZE = "72" diff --git a/examples/mspm0c1104/Cargo.toml b/examples/mspm0c1104/Cargo.toml new file mode 100644 index 000000000..79f9c0959 --- /dev/null +++ b/examples/mspm0c1104/Cargo.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-mspm0-c1104-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = ["mspm0c1104dgs20", "defmt", "rt", "time-driver-any"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "1.0.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7.0"} +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +# The chip only has 1KB of ram, so we must optimize binaries regardless +[profile.dev] +debug = 0 +opt-level = "z" +lto = true +codegen-units = 1 +# strip = true + +[profile.release] +debug = 0 +opt-level = "z" +lto = true +codegen-units = 1 diff --git a/examples/mspm0c1104/README.md b/examples/mspm0c1104/README.md new file mode 100644 index 000000000..86b6c3918 --- /dev/null +++ b/examples/mspm0c1104/README.md @@ -0,0 +1,27 @@ +# Examples for MSPM0C1104 + +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +A large number of the examples are written for the [LP-MSPM0C1104](https://www.ti.com/tool/LP-MSPM0C1104) board. + +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for C1104 it should be `probe-rs run --chip MSPM0C1104`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-mspm0` feature. For the LP-MSPM0C1104 it should be `mspm0c1104dgs20`. Look in the `Cargo.toml` file of the `embassy-mspm0` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/mspm0c1104/build.rs b/examples/mspm0c1104/build.rs new file mode 100644 index 000000000..2d777c2d3 --- /dev/null +++ b/examples/mspm0c1104/build.rs @@ -0,0 +1,37 @@ +//! 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"); + // You must tell cargo to link interrupt groups if the rt feature is enabled. + println!("cargo:rustc-link-arg-bins=-Tinterrupt_group.x"); +} diff --git a/examples/mspm0c1104/memory.x b/examples/mspm0c1104/memory.x new file mode 100644 index 000000000..a9108835a --- /dev/null +++ b/examples/mspm0c1104/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 16K + RAM : ORIGIN = 0x20000000, LENGTH = 1K +} diff --git a/examples/mspm0c1104/src/bin/blinky.rs b/examples/mspm0c1104/src/bin/blinky.rs new file mode 100644 index 000000000..0d974cc5e --- /dev/null +++ b/examples/mspm0c1104/src/bin/blinky.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Level, Output}; +use embassy_mspm0::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Config::default()); + + let mut led1 = Output::new(p.PA22, Level::Low); + led1.set_inversion(true); + + loop { + Timer::after_millis(400).await; + + info!("Toggle"); + led1.toggle(); + } +} diff --git a/examples/mspm0c1104/src/bin/button.rs b/examples/mspm0c1104/src/bin/button.rs new file mode 100644 index 000000000..7face1618 --- /dev/null +++ b/examples/mspm0c1104/src/bin/button.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Input, Level, Output, Pull}; +use embassy_mspm0::Config; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Config::default()); + + let led1 = p.PA22; + let s2 = p.PA16; + + let mut led1 = Output::new(led1, Level::Low); + + let mut s2 = Input::new(s2, Pull::Up); + + // led1 is active low + led1.set_high(); + + loop { + s2.wait_for_falling_edge().await; + + info!("Switch 2 was pressed"); + + led1.toggle(); + } +} diff --git a/examples/mspm0c1104/src/bin/uart.rs b/examples/mspm0c1104/src/bin/uart.rs new file mode 100644 index 000000000..da611aaac --- /dev/null +++ b/examples/mspm0c1104/src/bin/uart.rs @@ -0,0 +1,35 @@ +//! Example of using blocking uart +//! +//! This uses the virtual COM port provided on the LP-MSPM0C1104 board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::uart::{Config, Uart}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Default::default()); + + let instance = p.UART0; + let tx = p.PA27; + let rx = p.PA26; + + let config = Config::default(); + let mut uart = unwrap!(Uart::new_blocking(instance, rx, tx, config)); + + unwrap!(uart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + + loop { + unwrap!(uart.blocking_read(&mut buf)); + unwrap!(uart.blocking_write(&buf)); + } +} diff --git a/examples/mspm0g3507/.cargo/config.toml b/examples/mspm0g3507/.cargo/config.toml new file mode 100644 index 000000000..34c720cdd --- /dev/null +++ b/examples/mspm0g3507/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace MSPM0G3507 with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip MSPM0G3507 --protocol=swd" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "debug" diff --git a/examples/mspm0g3507/Cargo.toml b/examples/mspm0g3507/Cargo.toml new file mode 100644 index 000000000..b6621c9c5 --- /dev/null +++ b/examples/mspm0g3507/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-mspm0-g3507-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = ["mspm0g3507pm", "defmt", "rt", "time-driver-any"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "1.0.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7.0"} +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/examples/mspm0g3507/README.md b/examples/mspm0g3507/README.md new file mode 100644 index 000000000..be91dc5a0 --- /dev/null +++ b/examples/mspm0g3507/README.md @@ -0,0 +1,27 @@ +# Examples for MSPM0M3507 + +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +A large number of the examples are written for the [LP-MSPM0G3507](https://www.ti.com/tool/LP-MSPM0G3507) board. + +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for G3507 it should be `probe-rs run --chip MSPM0G3507`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-mspm0` feature. For the LP-MSPM0G3507 it should be `mspm0g3507pm`. Look in the `Cargo.toml` file of the `embassy-mspm0` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/mspm0g3507/build.rs b/examples/mspm0g3507/build.rs new file mode 100644 index 000000000..2d777c2d3 --- /dev/null +++ b/examples/mspm0g3507/build.rs @@ -0,0 +1,37 @@ +//! 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"); + // You must tell cargo to link interrupt groups if the rt feature is enabled. + println!("cargo:rustc-link-arg-bins=-Tinterrupt_group.x"); +} diff --git a/examples/mspm0g3507/memory.x b/examples/mspm0g3507/memory.x new file mode 100644 index 000000000..37e381fbd --- /dev/null +++ b/examples/mspm0g3507/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 128K + /* Select non-parity range of SRAM due to SRAM_ERR_01 errata in SLAZ758 */ + RAM : ORIGIN = 0x20200000, LENGTH = 32K +} diff --git a/examples/mspm0g3507/src/bin/blinky.rs b/examples/mspm0g3507/src/bin/blinky.rs new file mode 100644 index 000000000..055a5cd81 --- /dev/null +++ b/examples/mspm0g3507/src/bin/blinky.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Level, Output}; +use embassy_mspm0::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Config::default()); + + let mut led1 = Output::new(p.PA0, Level::Low); + led1.set_inversion(true); + + loop { + Timer::after_millis(400).await; + + info!("Toggle"); + led1.toggle(); + } +} diff --git a/examples/mspm0g3507/src/bin/button.rs b/examples/mspm0g3507/src/bin/button.rs new file mode 100644 index 000000000..cde1f2892 --- /dev/null +++ b/examples/mspm0g3507/src/bin/button.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Input, Level, Output, Pull}; +use embassy_mspm0::Config; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Config::default()); + + let led1 = p.PA0; + let s2 = p.PB21; + + let mut led1 = Output::new(led1, Level::Low); + + let mut s2 = Input::new(s2, Pull::Up); + + // led1 is active low + led1.set_high(); + + loop { + s2.wait_for_falling_edge().await; + + info!("Switch 2 was pressed"); + + led1.toggle(); + } +} diff --git a/examples/mspm0g3507/src/bin/uart.rs b/examples/mspm0g3507/src/bin/uart.rs new file mode 100644 index 000000000..7e7e6db0e --- /dev/null +++ b/examples/mspm0g3507/src/bin/uart.rs @@ -0,0 +1,35 @@ +//! Example of using blocking uart +//! +//! This uses the virtual COM port provided on the LP-MSPM0G3507 board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::uart::{Config, Uart}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Default::default()); + + let instance = p.UART0; + let tx = p.PA10; + let rx = p.PA11; + + let config = Config::default(); + let mut uart = unwrap!(Uart::new_blocking(instance, rx, tx, config)); + + unwrap!(uart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + + loop { + unwrap!(uart.blocking_read(&mut buf)); + unwrap!(uart.blocking_write(&buf)); + } +} diff --git a/examples/mspm0g3519/.cargo/config.toml b/examples/mspm0g3519/.cargo/config.toml new file mode 100644 index 000000000..7bba4646f --- /dev/null +++ b/examples/mspm0g3519/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace MSPM0G3519 with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --restore-unwritten --verify --chip MSPM0G3519 --protocol=swd" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mspm0g3519/Cargo.toml b/examples/mspm0g3519/Cargo.toml new file mode 100644 index 000000000..fd0e97c01 --- /dev/null +++ b/examples/mspm0g3519/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-mspm0-g3519-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = ["mspm0g3519pz", "defmt", "rt", "time-driver-any"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "1.0.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7.0"} +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/examples/mspm0g3519/README.md b/examples/mspm0g3519/README.md new file mode 100644 index 000000000..c392c9e25 --- /dev/null +++ b/examples/mspm0g3519/README.md @@ -0,0 +1,27 @@ +# Examples for MSPM0G3519 + +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +A large number of the examples are written for the [LP-MSPM0G3519](https://www.ti.com/tool/LP-MSPM0G3519) board. + +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for G3519 it should be `probe-rs run --chip MSPM0G3519`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-mspm0` feature. For the LP-MSPM0G3519 it should be `mspm0g3519pz`. Look in the `Cargo.toml` file of the `embassy-mspm0` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/mspm0g3519/build.rs b/examples/mspm0g3519/build.rs new file mode 100644 index 000000000..2d777c2d3 --- /dev/null +++ b/examples/mspm0g3519/build.rs @@ -0,0 +1,37 @@ +//! 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"); + // You must tell cargo to link interrupt groups if the rt feature is enabled. + println!("cargo:rustc-link-arg-bins=-Tinterrupt_group.x"); +} diff --git a/examples/mspm0g3519/memory.x b/examples/mspm0g3519/memory.x new file mode 100644 index 000000000..e6e0ec9e9 --- /dev/null +++ b/examples/mspm0g3519/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 512K + /* Select non-parity range of SRAM due to SRAM_ERR_01 errata in SLAZ758 */ + RAM : ORIGIN = 0x20200000, LENGTH = 128K +} diff --git a/examples/mspm0g3519/src/bin/blinky.rs b/examples/mspm0g3519/src/bin/blinky.rs new file mode 100644 index 000000000..055a5cd81 --- /dev/null +++ b/examples/mspm0g3519/src/bin/blinky.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Level, Output}; +use embassy_mspm0::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Config::default()); + + let mut led1 = Output::new(p.PA0, Level::Low); + led1.set_inversion(true); + + loop { + Timer::after_millis(400).await; + + info!("Toggle"); + led1.toggle(); + } +} diff --git a/examples/mspm0g3519/src/bin/button.rs b/examples/mspm0g3519/src/bin/button.rs new file mode 100644 index 000000000..c81cc2918 --- /dev/null +++ b/examples/mspm0g3519/src/bin/button.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Input, Level, Output, Pull}; +use embassy_mspm0::Config; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Config::default()); + + let led1 = p.PA0; + let s2 = p.PB3; + + let mut led1 = Output::new(led1, Level::Low); + + let mut s2 = Input::new(s2, Pull::Up); + + // led1 is active low + led1.set_high(); + + loop { + s2.wait_for_falling_edge().await; + + info!("Switch 2 was pressed"); + + led1.toggle(); + } +} diff --git a/examples/mspm0g3519/src/bin/uart.rs b/examples/mspm0g3519/src/bin/uart.rs new file mode 100644 index 000000000..498377c61 --- /dev/null +++ b/examples/mspm0g3519/src/bin/uart.rs @@ -0,0 +1,35 @@ +//! Example of using blocking uart +//! +//! This uses the virtual COM port provided on the LP-MSPM0G3519 board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::uart::{Config, Uart}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Default::default()); + + let instance = p.UART0; + let tx = p.PA10; + let rx = p.PA11; + + let config = Config::default(); + let mut uart = unwrap!(Uart::new_blocking(instance, rx, tx, config)); + + unwrap!(uart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + + loop { + unwrap!(uart.blocking_read(&mut buf)); + unwrap!(uart.blocking_write(&buf)); + } +} diff --git a/examples/mspm0l1306/.cargo/config.toml b/examples/mspm0l1306/.cargo/config.toml new file mode 100644 index 000000000..93f148a71 --- /dev/null +++ b/examples/mspm0l1306/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace MSPM0L1306 with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip MSPM0L1306 --protocol=swd" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mspm0l1306/Cargo.toml b/examples/mspm0l1306/Cargo.toml new file mode 100644 index 000000000..6b1125810 --- /dev/null +++ b/examples/mspm0l1306/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-mspm0-l1306-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = ["mspm0l1306rhb", "defmt", "rt", "time-driver-any"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "1.0.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7.0"} +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/examples/mspm0l1306/README.md b/examples/mspm0l1306/README.md new file mode 100644 index 000000000..4d698e0fa --- /dev/null +++ b/examples/mspm0l1306/README.md @@ -0,0 +1,27 @@ +# Examples for MSPM0L1306 + +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +A large number of the examples are written for the [LP-MSPM0L1306](https://www.ti.com/tool/LP-MSPM0L1306) board. + +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L1306 it should be `probe-rs run --chip MSPM0L1306`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-mspm0` feature. For the LP-MSPM0L1306 it should be `mspm0l1306rhb`. Look in the `Cargo.toml` file of the `embassy-mspm0` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/mspm0l1306/build.rs b/examples/mspm0l1306/build.rs new file mode 100644 index 000000000..2d777c2d3 --- /dev/null +++ b/examples/mspm0l1306/build.rs @@ -0,0 +1,37 @@ +//! 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"); + // You must tell cargo to link interrupt groups if the rt feature is enabled. + println!("cargo:rustc-link-arg-bins=-Tinterrupt_group.x"); +} diff --git a/examples/mspm0l1306/memory.x b/examples/mspm0l1306/memory.x new file mode 100644 index 000000000..d93b61f44 --- /dev/null +++ b/examples/mspm0l1306/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 64K + RAM : ORIGIN = 0x20000000, LENGTH = 4K +} diff --git a/examples/mspm0l1306/src/bin/blinky.rs b/examples/mspm0l1306/src/bin/blinky.rs new file mode 100644 index 000000000..055a5cd81 --- /dev/null +++ b/examples/mspm0l1306/src/bin/blinky.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Level, Output}; +use embassy_mspm0::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Config::default()); + + let mut led1 = Output::new(p.PA0, Level::Low); + led1.set_inversion(true); + + loop { + Timer::after_millis(400).await; + + info!("Toggle"); + led1.toggle(); + } +} diff --git a/examples/mspm0l1306/src/bin/button.rs b/examples/mspm0l1306/src/bin/button.rs new file mode 100644 index 000000000..d8c85947f --- /dev/null +++ b/examples/mspm0l1306/src/bin/button.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Input, Level, Output, Pull}; +use embassy_mspm0::Config; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Config::default()); + + let led1 = p.PA0; + let s2 = p.PA14; + + let mut led1 = Output::new(led1, Level::Low); + + let mut s2 = Input::new(s2, Pull::Up); + + // led1 is active low + led1.set_high(); + + loop { + s2.wait_for_falling_edge().await; + + info!("Switch 2 was pressed"); + + led1.toggle(); + } +} diff --git a/examples/mspm0l1306/src/bin/uart.rs b/examples/mspm0l1306/src/bin/uart.rs new file mode 100644 index 000000000..95c56fdd3 --- /dev/null +++ b/examples/mspm0l1306/src/bin/uart.rs @@ -0,0 +1,35 @@ +//! Example of using blocking uart +//! +//! This uses the virtual COM port provided on the LP-MSPM0L1306 board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::uart::{Config, Uart}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Default::default()); + + let instance = p.UART0; + let tx = p.PA8; + let rx = p.PA9; + + let config = Config::default(); + let mut uart = unwrap!(Uart::new_blocking(instance, rx, tx, config)); + + unwrap!(uart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + + loop { + unwrap!(uart.blocking_read(&mut buf)); + unwrap!(uart.blocking_write(&buf)); + } +} diff --git a/examples/mspm0l2228/.cargo/config.toml b/examples/mspm0l2228/.cargo/config.toml new file mode 100644 index 000000000..f383afd9e --- /dev/null +++ b/examples/mspm0l2228/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace MSPM0L2228 with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --restore-unwritten --verify --chip MSPM0L2228 --protocol=swd" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/mspm0l2228/Cargo.toml b/examples/mspm0l2228/Cargo.toml new file mode 100644 index 000000000..08dfd5ff6 --- /dev/null +++ b/examples/mspm0l2228/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-mspm0-l2228-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = ["mspm0l2228pn", "defmt", "rt", "time-driver-any"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt"] } +panic-halt = "1.0.0" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7.0"} +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +panic-semihosting = "0.6.0" + +[profile.release] +debug = 2 diff --git a/examples/mspm0l2228/README.md b/examples/mspm0l2228/README.md new file mode 100644 index 000000000..191022258 --- /dev/null +++ b/examples/mspm0l2228/README.md @@ -0,0 +1,27 @@ +# Examples for MSPM0L2228 + +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +A large number of the examples are written for the [LP-MSPM0L2228](https://www.ti.com/tool/LP-MSPM0L2228) board. + +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L2228 it should be `probe-rs run --chip MSPM0L2228`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-mspm0` feature. For example for LP-MSPM0L2228 it should be `mspm0l2228pn`. Look in the `Cargo.toml` file of the `embassy-mspm0` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/mspm0l2228/build.rs b/examples/mspm0l2228/build.rs new file mode 100644 index 000000000..2d777c2d3 --- /dev/null +++ b/examples/mspm0l2228/build.rs @@ -0,0 +1,37 @@ +//! 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"); + // You must tell cargo to link interrupt groups if the rt feature is enabled. + println!("cargo:rustc-link-arg-bins=-Tinterrupt_group.x"); +} diff --git a/examples/mspm0l2228/memory.x b/examples/mspm0l2228/memory.x new file mode 100644 index 000000000..aba414a88 --- /dev/null +++ b/examples/mspm0l2228/memory.x @@ -0,0 +1,6 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 256K + /* Select non-parity range of SRAM due to SRAM_ERR_01 errata in SLAZ758 */ + RAM : ORIGIN = 0x20200000, LENGTH = 32K +} diff --git a/examples/mspm0l2228/src/bin/blinky.rs b/examples/mspm0l2228/src/bin/blinky.rs new file mode 100644 index 000000000..055a5cd81 --- /dev/null +++ b/examples/mspm0l2228/src/bin/blinky.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Level, Output}; +use embassy_mspm0::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + let p = embassy_mspm0::init(Config::default()); + + let mut led1 = Output::new(p.PA0, Level::Low); + led1.set_inversion(true); + + loop { + Timer::after_millis(400).await; + + info!("Toggle"); + led1.toggle(); + } +} diff --git a/examples/mspm0l2228/src/bin/button.rs b/examples/mspm0l2228/src/bin/button.rs new file mode 100644 index 000000000..47bfd274b --- /dev/null +++ b/examples/mspm0l2228/src/bin/button.rs @@ -0,0 +1,33 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::gpio::{Input, Level, Output, Pull}; +use embassy_mspm0::Config; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Config::default()); + + let led1 = p.PA0; + let s2 = p.PB8; + + let mut led1 = Output::new(led1, Level::Low); + + let mut s2 = Input::new(s2, Pull::Up); + + // led1 is active low + led1.set_high(); + + loop { + s2.wait_for_falling_edge().await; + + info!("Switch 2 was pressed"); + + led1.toggle(); + } +} diff --git a/examples/mspm0l2228/src/bin/uart.rs b/examples/mspm0l2228/src/bin/uart.rs new file mode 100644 index 000000000..a266add47 --- /dev/null +++ b/examples/mspm0l2228/src/bin/uart.rs @@ -0,0 +1,35 @@ +//! Example of using blocking uart +//! +//! This uses the virtual COM port provided on the LP-MSPM0L2228 board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_mspm0::uart::{Config, Uart}; +use {defmt_rtt as _, panic_halt as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + info!("Hello world!"); + + let p = embassy_mspm0::init(Default::default()); + + let instance = p.UART0; + let tx = p.PA10; + let rx = p.PA11; + + let config = Config::default(); + let mut uart = unwrap!(Uart::new_blocking(instance, rx, tx, config)); + + unwrap!(uart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + + loop { + unwrap!(uart.blocking_read(&mut buf)); + unwrap!(uart.blocking_write(&buf)); + } +} diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml index 98a678815..d9e8ca2f9 100644 --- a/examples/nrf-rtos-trace/Cargo.toml +++ b/examples/nrf-rtos-trace/Cargo.toml @@ -15,15 +15,14 @@ log = [ ] [dependencies] -embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time" } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time" } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3" } -rand = { version = "0.8.4", default-features = false } +panic-probe = "1.0.0" serde = { version = "1.0.136", default-features = false } rtos-trace = "0.1.3" systemview-target = { version = "0.1.2", features = ["callbacks-app", "callbacks-os", "log", "cortex-m"] } diff --git a/examples/nrf51/Cargo.toml b/examples/nrf51/Cargo.toml index 93a19bea7..91f78737f 100644 --- a/examples/nrf51/Cargo.toml +++ b/examples/nrf51/Cargo.toml @@ -5,16 +5,16 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" 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"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } [profile.release] debug = 2 diff --git a/examples/nrf52810/Cargo.toml b/examples/nrf52810/Cargo.toml index 0e3e81c3f..87da89efe 100644 --- a/examples/nrf52810/Cargo.toml +++ b/examples/nrf52810/Cargo.toml @@ -6,19 +6,19 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" fixed = "1.10.0" static_cell = { version = "2" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } [profile.release] debug = 2 diff --git a/examples/nrf52840-rtic/Cargo.toml b/examples/nrf52840-rtic/Cargo.toml index 7fae7aefc..afd269f72 100644 --- a/examples/nrf52840-rtic/Cargo.toml +++ b/examples/nrf52840-rtic/Cargo.toml @@ -8,16 +8,17 @@ license = "MIT OR Apache-2.0" rtic = { version = "2", features = ["thumbv7-backend"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime", "generic-queue"] } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = [ "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime"] } +embassy-time-queue-utils = { version = "0.1", path = "../../embassy-time-queue-utils", features = ["generic-queue-8"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = [ "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } [profile.release] debug = 2 diff --git a/examples/nrf52840-rtic/src/bin/blinky.rs b/examples/nrf52840-rtic/src/bin/blinky.rs index 060bb9ebc..719e22729 100644 --- a/examples/nrf52840-rtic/src/bin/blinky.rs +++ b/examples/nrf52840-rtic/src/bin/blinky.rs @@ -4,11 +4,11 @@ use {defmt_rtt as _, panic_probe as _}; -#[rtic::app(device = embassy_nrf, peripherals = false, dispatchers = [SWI0_EGU0, SWI1_EGU1])] +#[rtic::app(device = embassy_nrf, peripherals = false, dispatchers = [EGU0_SWI0, EGU1_SWI1])] mod app { use defmt::info; use embassy_nrf::gpio::{Level, Output, OutputDrive}; - use embassy_nrf::peripherals; + use embassy_nrf::{peripherals, Peri}; use embassy_time::Timer; #[shared] @@ -28,7 +28,7 @@ mod app { } #[task(priority = 1)] - async fn blink(_cx: blink::Context, pin: peripherals::P0_13) { + async fn blink(_cx: blink::Context, pin: Peri<'static, peripherals::P0_13>) { let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); loop { diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index 17fa6234d..4140e49d2 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -6,26 +6,26 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embedded-io = { version = "0.6.0", features = ["defmt-03"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } -embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } -embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } +embassy-net-esp-hosted = { version = "0.2.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embassy-net-enc28j60 = { version = "0.2.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" fixed = "1.10.0" static_cell = { version = "2" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } -rand = { version = "0.8.4", default-features = false } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +rand = { version = "0.9.0", default-features = false } embedded-storage = "0.3.1" usbd-hid = "0.8.1" serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf52840/src/bin/buffered_uart.rs b/examples/nrf52840/src/bin/buffered_uart.rs index 6ac72bcaf..f0a066818 100644 --- a/examples/nrf52840/src/bin/buffered_uart.rs +++ b/examples/nrf52840/src/bin/buffered_uart.rs @@ -9,7 +9,7 @@ use embedded_io_async::Write; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - UARTE0_UART0 => buffered_uarte::InterruptHandler; + UARTE0 => buffered_uarte::InterruptHandler; }); #[embassy_executor::main] @@ -28,9 +28,9 @@ async fn main(_spawner: Spawner) { p.PPI_CH0, p.PPI_CH1, p.PPI_GROUP0, - Irqs, p.P0_08, p.P0_06, + Irqs, config, &mut rx_buffer, &mut tx_buffer, diff --git a/examples/nrf52840/src/bin/channel.rs b/examples/nrf52840/src/bin/channel.rs index 7fcea9dbd..e06ba1c73 100644 --- a/examples/nrf52840/src/bin/channel.rs +++ b/examples/nrf52840/src/bin/channel.rs @@ -35,8 +35,8 @@ async fn main(spawner: Spawner) { loop { match CHANNEL.receive().await { - LedState::On => led.set_high(), - LedState::Off => led.set_low(), + LedState::On => led.set_low(), + LedState::Off => led.set_high(), } } } diff --git a/examples/nrf52840/src/bin/channel_sender_receiver.rs b/examples/nrf52840/src/bin/channel_sender_receiver.rs index 3095a04ec..74c62ca20 100644 --- a/examples/nrf52840/src/bin/channel_sender_receiver.rs +++ b/examples/nrf52840/src/bin/channel_sender_receiver.rs @@ -3,7 +3,8 @@ use defmt::unwrap; use embassy_executor::Spawner; -use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; +use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive}; +use embassy_nrf::Peri; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::channel::{Channel, Receiver, Sender}; use embassy_time::Timer; @@ -28,13 +29,13 @@ async fn send_task(sender: Sender<'static, NoopRawMutex, LedState, 1>) { } #[embassy_executor::task] -async fn recv_task(led: AnyPin, receiver: Receiver<'static, NoopRawMutex, LedState, 1>) { +async fn recv_task(led: Peri<'static, AnyPin>, receiver: Receiver<'static, NoopRawMutex, LedState, 1>) { let mut led = Output::new(led, Level::Low, OutputDrive::Standard); loop { match receiver.receive().await { - LedState::On => led.set_high(), - LedState::Off => led.set_low(), + LedState::On => led.set_low(), + LedState::Off => led.set_high(), } } } @@ -45,5 +46,5 @@ async fn main(spawner: Spawner) { let channel = CHANNEL.init(Channel::new()); unwrap!(spawner.spawn(send_task(channel.sender()))); - unwrap!(spawner.spawn(recv_task(p.P0_13.degrade(), channel.receiver()))); + unwrap!(spawner.spawn(recv_task(p.P0_13.into(), channel.receiver()))); } diff --git a/examples/nrf52840/src/bin/ethernet_enc28j60.rs b/examples/nrf52840/src/bin/ethernet_enc28j60.rs index 94cf09c88..0946492fe 100644 --- a/examples/nrf52840/src/bin/ethernet_enc28j60.rs +++ b/examples/nrf52840/src/bin/ethernet_enc28j60.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_enc28j60::Enc28j60; use embassy_nrf::gpio::{Level, Output, OutputDrive}; use embassy_nrf::rng::Rng; @@ -23,11 +23,12 @@ bind_interrupts!(struct Irqs { #[embassy_executor::task] async fn net_task( - stack: &'static Stack< + mut runner: embassy_net::Runner< + 'static, Enc28j60, Output<'static>, Delay>, Output<'static>>, >, ) -> ! { - stack.run().await + runner.run().await } #[embassy_executor::main] @@ -67,12 +68,9 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell< - Stack, Output<'static>, Delay>, Output<'static>>>, - > = StaticCell::new(); - let stack = STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf52840/src/bin/ieee802154_receive.rs b/examples/nrf52840/src/bin/ieee802154_receive.rs new file mode 100644 index 000000000..ede8fca65 --- /dev/null +++ b/examples/nrf52840/src/bin/ieee802154_receive.rs @@ -0,0 +1,38 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::config::{Config, HfclkSource}; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::radio::ieee802154::{self, Packet}; +use embassy_nrf::{peripherals, radio}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +embassy_nrf::bind_interrupts!(struct Irqs { + RADIO => radio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.hfclk_source = HfclkSource::ExternalXtal; + let peripherals = embassy_nrf::init(config); + + // assumes LED on P0_15 with active-high polarity + let mut gpo_led = Output::new(peripherals.P0_15, Level::Low, OutputDrive::Standard); + + let mut radio = ieee802154::Radio::new(peripherals.RADIO, Irqs); + let mut packet = Packet::new(); + + loop { + gpo_led.set_low(); + let rv = radio.receive(&mut packet).await; + gpo_led.set_high(); + match rv { + Err(_) => defmt::error!("receive() Err"), + Ok(_) => defmt::info!("receive() {:?}", *packet), + } + Timer::after_millis(100u64).await; + } +} diff --git a/examples/nrf52840/src/bin/ieee802154_send.rs b/examples/nrf52840/src/bin/ieee802154_send.rs new file mode 100644 index 000000000..7af9d1d06 --- /dev/null +++ b/examples/nrf52840/src/bin/ieee802154_send.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::config::{Config, HfclkSource}; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::radio::ieee802154::{self, Packet}; +use embassy_nrf::{peripherals, radio}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +embassy_nrf::bind_interrupts!(struct Irqs { + RADIO => radio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.hfclk_source = HfclkSource::ExternalXtal; + let peripherals = embassy_nrf::init(config); + + // assumes LED on P0_15 with active-high polarity + let mut gpo_led = Output::new(peripherals.P0_15, Level::Low, OutputDrive::Standard); + + let mut radio = ieee802154::Radio::new(peripherals.RADIO, Irqs); + let mut packet = Packet::new(); + + loop { + packet.copy_from_slice(&[0_u8; 16]); + gpo_led.set_high(); + let rv = radio.try_send(&mut packet).await; + gpo_led.set_low(); + match rv { + Err(_) => defmt::error!("try_send() Err"), + Ok(_) => defmt::info!("try_send() {:?}", *packet), + } + Timer::after_millis(1000u64).await; + } +} diff --git a/examples/nrf52840/src/bin/multiprio.rs b/examples/nrf52840/src/bin/multiprio.rs index 797be93a7..d58613da4 100644 --- a/examples/nrf52840/src/bin/multiprio.rs +++ b/examples/nrf52840/src/bin/multiprio.rs @@ -112,12 +112,12 @@ static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); static EXECUTOR_LOW: StaticCell = StaticCell::new(); #[interrupt] -unsafe fn SWI1_EGU1() { +unsafe fn EGU1_SWI1() { EXECUTOR_HIGH.on_interrupt() } #[interrupt] -unsafe fn SWI0_EGU0() { +unsafe fn EGU0_SWI0() { EXECUTOR_MED.on_interrupt() } @@ -127,14 +127,14 @@ fn main() -> ! { let _p = embassy_nrf::init(Default::default()); - // High-priority executor: SWI1_EGU1, priority level 6 - interrupt::SWI1_EGU1.set_priority(Priority::P6); - let spawner = EXECUTOR_HIGH.start(interrupt::SWI1_EGU1); + // High-priority executor: EGU1_SWI1, priority level 6 + interrupt::EGU1_SWI1.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::EGU1_SWI1); unwrap!(spawner.spawn(run_high())); - // Medium-priority executor: SWI0_EGU0, priority level 7 - interrupt::SWI0_EGU0.set_priority(Priority::P7); - let spawner = EXECUTOR_MED.start(interrupt::SWI0_EGU0); + // Medium-priority executor: EGU0_SWI0, priority level 7 + interrupt::EGU0_SWI0.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::EGU0_SWI0); unwrap!(spawner.spawn(run_med())); // Low priority executor: runs in thread mode, using WFE/SEV diff --git a/examples/nrf52840/src/bin/nfct.rs b/examples/nrf52840/src/bin/nfct.rs new file mode 100644 index 000000000..d559d006a --- /dev/null +++ b/examples/nrf52840/src/bin/nfct.rs @@ -0,0 +1,79 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::config::HfclkSource; +use embassy_nrf::nfct::{Config as NfcConfig, NfcId, NfcT}; +use embassy_nrf::{bind_interrupts, nfct}; +use {defmt_rtt as _, embassy_nrf as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + NFCT => nfct::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_nrf::config::Config::default(); + config.hfclk_source = HfclkSource::ExternalXtal; + let p = embassy_nrf::init(config); + + dbg!("Setting up..."); + let config = NfcConfig { + nfcid1: NfcId::DoubleSize([0x04, 0x68, 0x95, 0x71, 0xFA, 0x5C, 0x64]), + sdd_pat: nfct::SddPat::SDD00100, + plat_conf: 0b0000, + protocol: nfct::SelResProtocol::Type4A, + }; + + let mut nfc = NfcT::new(p.NFCT, Irqs, &config); + + let mut buf = [0u8; 256]; + + loop { + info!("activating"); + nfc.activate().await; + + loop { + info!("rxing"); + let n = match nfc.receive(&mut buf).await { + Ok(n) => n, + Err(e) => { + error!("rx error {}", e); + break; + } + }; + let req = &buf[..n]; + info!("received frame {:02x}", req); + + let mut deselect = false; + let resp = match req { + [0xe0, ..] => { + info!("Got RATS, tx'ing ATS"); + &[0x06, 0x77, 0x77, 0x81, 0x02, 0x80][..] + } + [0xc2] => { + info!("Got deselect!"); + deselect = true; + &[0xc2] + } + _ => { + info!("Got unknown command!"); + &[0xFF] + } + }; + + match nfc.transmit(resp).await { + Ok(()) => {} + Err(e) => { + error!("tx error {}", e); + break; + } + } + + if deselect { + break; + } + } + } +} diff --git a/examples/nrf52840/src/bin/pdm_continuous.rs b/examples/nrf52840/src/bin/pdm_continuous.rs index e948203a5..0d76636b0 100644 --- a/examples/nrf52840/src/bin/pdm_continuous.rs +++ b/examples/nrf52840/src/bin/pdm_continuous.rs @@ -20,14 +20,14 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_p: Spawner) { - let mut p = embassy_nrf::init(Default::default()); + let p = embassy_nrf::init(Default::default()); let mut config = Config::default(); // Pins are correct for the onboard microphone on the Feather nRF52840 Sense. config.frequency = Frequency::_1280K; // 16 kHz sample rate config.ratio = Ratio::RATIO80; config.operation_mode = OperationMode::Mono; config.gain_left = I7F1::from_bits(5); // 2.5 dB - let mut pdm = Pdm::new(p.PDM, Irqs, &mut p.P0_00, &mut p.P0_01, config); + let mut pdm = Pdm::new(p.PDM, Irqs, p.P0_00, p.P0_01, config); let mut bufs = [[0; 1024]; 2]; diff --git a/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs index 751cf4425..df8da8800 100644 --- a/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs +++ b/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs @@ -14,6 +14,16 @@ use {defmt_rtt as _, panic_probe as _}; // https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf. // This demo lights up a single LED in blue. It then proceeds // to pulsate the LED rapidly. +// +// /!\ NOTE FOR nRF52840-DK users /!\ +// +// If you're using the nRF52840-DK, the default "Vdd" power source +// will set the GPIO I/O voltage to 3.0v, using the onboard regulator. +// This can sometimes not be enough to drive the WS2812B signal if you +// are not using an external level shifter. If you set the board to "USB" +// power instead (and provide power via the "nRF USB" connector), the board +// will instead power the I/Os at 3.3v, which is often enough (but still +// out of official spec) for the WS2812Bs to work properly. // In the following declarations, setting the high bit tells the PWM // to reverse polarity, which is what the WS2812B expects. diff --git a/examples/nrf52840/src/bin/qspi_lowpower.rs b/examples/nrf52840/src/bin/qspi_lowpower.rs index 516c9b481..238a0d941 100644 --- a/examples/nrf52840/src/bin/qspi_lowpower.rs +++ b/examples/nrf52840/src/bin/qspi_lowpower.rs @@ -37,14 +37,14 @@ async fn main(_p: Spawner) { }); let mut q = qspi::Qspi::new( - &mut p.QSPI, + p.QSPI.reborrow(), Irqs, - &mut p.P0_19, - &mut p.P0_17, - &mut p.P0_20, - &mut p.P0_21, - &mut p.P0_22, - &mut p.P0_23, + p.P0_19.reborrow(), + p.P0_17.reborrow(), + p.P0_20.reborrow(), + p.P0_21.reborrow(), + p.P0_22.reborrow(), + p.P0_23.reborrow(), config, ); diff --git a/examples/nrf52840/src/bin/rng.rs b/examples/nrf52840/src/bin/rng.rs old mode 100644 new mode 100755 index 326054c9a..f32d17cd9 --- a/examples/nrf52840/src/bin/rng.rs +++ b/examples/nrf52840/src/bin/rng.rs @@ -22,7 +22,7 @@ async fn main(_spawner: Spawner) { defmt::info!("Some random bytes: {:?}", bytes); // Sync API with `rand` - defmt::info!("A random number from 1 to 10: {:?}", rng.gen_range(1..=10)); + defmt::info!("A random number from 1 to 10: {:?}", rng.random_range(1..=10)); let mut bytes = [0; 1024]; rng.fill_bytes(&mut bytes).await; diff --git a/examples/nrf52840/src/bin/saadc.rs b/examples/nrf52840/src/bin/saadc.rs index 653b7d606..cf2d860ab 100644 --- a/examples/nrf52840/src/bin/saadc.rs +++ b/examples/nrf52840/src/bin/saadc.rs @@ -16,7 +16,7 @@ bind_interrupts!(struct Irqs { async fn main(_p: Spawner) { let mut p = embassy_nrf::init(Default::default()); let config = Config::default(); - let channel_config = ChannelConfig::single_ended(&mut p.P0_02); + let channel_config = ChannelConfig::single_ended(p.P0_02.reborrow()); let mut saadc = Saadc::new(p.SAADC, Irqs, config, [channel_config]); loop { diff --git a/examples/nrf52840/src/bin/saadc_continuous.rs b/examples/nrf52840/src/bin/saadc_continuous.rs index f76fa3570..e8f169c8c 100644 --- a/examples/nrf52840/src/bin/saadc_continuous.rs +++ b/examples/nrf52840/src/bin/saadc_continuous.rs @@ -18,9 +18,9 @@ bind_interrupts!(struct Irqs { async fn main(_p: Spawner) { let mut p = embassy_nrf::init(Default::default()); let config = Config::default(); - let channel_1_config = ChannelConfig::single_ended(&mut p.P0_02); - let channel_2_config = ChannelConfig::single_ended(&mut p.P0_03); - let channel_3_config = ChannelConfig::single_ended(&mut p.P0_04); + let channel_1_config = ChannelConfig::single_ended(p.P0_02.reborrow()); + let channel_2_config = ChannelConfig::single_ended(p.P0_03.reborrow()); + let channel_3_config = ChannelConfig::single_ended(p.P0_04.reborrow()); let mut saadc = Saadc::new( p.SAADC, Irqs, @@ -40,9 +40,9 @@ async fn main(_p: Spawner) { saadc .run_task_sampler( - &mut p.TIMER0, - &mut p.PPI_CH0, - &mut p.PPI_CH1, + p.TIMER0.reborrow(), + p.PPI_CH0.reborrow(), + p.PPI_CH1.reborrow(), Frequency::F1MHz, 1000, // We want to sample at 1KHz &mut bufs, diff --git a/examples/nrf52840/src/bin/spis.rs b/examples/nrf52840/src/bin/spis.rs index 613cd37ab..4f28da07e 100644 --- a/examples/nrf52840/src/bin/spis.rs +++ b/examples/nrf52840/src/bin/spis.rs @@ -8,7 +8,7 @@ use embassy_nrf::{bind_interrupts, peripherals, spis}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - SPIM2_SPIS2_SPI2 => spis::InterruptHandler; + SPI2 => spis::InterruptHandler; }); #[embassy_executor::main] diff --git a/examples/nrf52840/src/bin/twim.rs b/examples/nrf52840/src/bin/twim.rs index a9a0765e8..e30a3855d 100644 --- a/examples/nrf52840/src/bin/twim.rs +++ b/examples/nrf52840/src/bin/twim.rs @@ -9,12 +9,13 @@ use defmt::*; use embassy_executor::Spawner; use embassy_nrf::twim::{self, Twim}; use embassy_nrf::{bind_interrupts, peripherals}; +use static_cell::ConstStaticCell; use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x50; bind_interrupts!(struct Irqs { - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler; + TWISPI0 => twim::InterruptHandler; }); #[embassy_executor::main] @@ -22,7 +23,8 @@ async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); info!("Initializing TWI..."); let config = twim::Config::default(); - let mut twi = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); + static RAM_BUFFER: ConstStaticCell<[u8; 16]> = ConstStaticCell::new([0; 16]); + let mut twi = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config, RAM_BUFFER.take()); info!("Reading..."); diff --git a/examples/nrf52840/src/bin/twim_lowpower.rs b/examples/nrf52840/src/bin/twim_lowpower.rs index c743614b8..f7380e20d 100644 --- a/examples/nrf52840/src/bin/twim_lowpower.rs +++ b/examples/nrf52840/src/bin/twim_lowpower.rs @@ -19,7 +19,7 @@ use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x50; bind_interrupts!(struct Irqs { - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler; + TWISPI0 => twim::InterruptHandler; }); #[embassy_executor::main] @@ -30,9 +30,17 @@ async fn main(_p: Spawner) { loop { info!("Initializing TWI..."); let config = twim::Config::default(); + let mut ram_buffer = [0u8; 16]; // Create the TWIM instance with borrowed singletons, so they're not consumed. - let mut twi = Twim::new(&mut p.TWISPI0, Irqs, &mut p.P0_03, &mut p.P0_04, config); + let mut twi = Twim::new( + p.TWISPI0.reborrow(), + Irqs, + p.P0_03.reborrow(), + p.P0_04.reborrow(), + config, + &mut ram_buffer, + ); info!("Reading..."); diff --git a/examples/nrf52840/src/bin/twis.rs b/examples/nrf52840/src/bin/twis.rs index 88bd4cceb..856b34140 100644 --- a/examples/nrf52840/src/bin/twis.rs +++ b/examples/nrf52840/src/bin/twis.rs @@ -10,7 +10,7 @@ use embassy_nrf::{bind_interrupts, peripherals}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twis::InterruptHandler; + TWISPI0 => twis::InterruptHandler; }); #[embassy_executor::main] diff --git a/examples/nrf52840/src/bin/uart.rs b/examples/nrf52840/src/bin/uart.rs index accaccea1..f9f8d74ab 100644 --- a/examples/nrf52840/src/bin/uart.rs +++ b/examples/nrf52840/src/bin/uart.rs @@ -7,7 +7,7 @@ use embassy_nrf::{bind_interrupts, peripherals, uarte}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - UARTE0_UART0 => uarte::InterruptHandler; + UARTE0 => uarte::InterruptHandler; }); #[embassy_executor::main] @@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let mut uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config); + let mut uart = uarte::Uarte::new(p.UARTE0, p.P0_08, p.P0_06, Irqs, config); info!("uarte initialized!"); diff --git a/examples/nrf52840/src/bin/uart_idle.rs b/examples/nrf52840/src/bin/uart_idle.rs index fa93bcf21..00e3ae904 100644 --- a/examples/nrf52840/src/bin/uart_idle.rs +++ b/examples/nrf52840/src/bin/uart_idle.rs @@ -8,7 +8,7 @@ use embassy_nrf::{bind_interrupts, uarte}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - UARTE0_UART0 => uarte::InterruptHandler; + UARTE0 => uarte::InterruptHandler; }); #[embassy_executor::main] @@ -18,7 +18,7 @@ async fn main(_spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config); + let uart = uarte::Uarte::new(p.UARTE0, p.P0_08, p.P0_06, Irqs, config); let (mut tx, mut rx) = uart.split_with_idle(p.TIMER0, p.PPI_CH0, p.PPI_CH1); info!("uarte initialized!"); diff --git a/examples/nrf52840/src/bin/uart_split.rs b/examples/nrf52840/src/bin/uart_split.rs index c7510a9a8..46be8f636 100644 --- a/examples/nrf52840/src/bin/uart_split.rs +++ b/examples/nrf52840/src/bin/uart_split.rs @@ -13,7 +13,7 @@ use {defmt_rtt as _, panic_probe as _}; static CHANNEL: Channel = Channel::new(); bind_interrupts!(struct Irqs { - UARTE0_UART0 => uarte::InterruptHandler; + UARTE0 => uarte::InterruptHandler; }); #[embassy_executor::main] @@ -23,7 +23,7 @@ async fn main(spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config); + let uart = uarte::Uarte::new(p.UARTE0, p.P0_08, p.P0_06, Irqs, config); let (mut tx, rx) = uart.split(); info!("uarte initialized!"); diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs index e56b215e3..49856012d 100644 --- a/examples/nrf52840/src/bin/usb_ethernet.rs +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -1,12 +1,10 @@ #![no_std] #![no_main] -use core::mem; - use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_nrf::rng::Rng; use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; use embassy_nrf::usb::Driver; @@ -20,7 +18,7 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { USBD => usb::InterruptHandler; - POWER_CLOCK => usb::vbus_detect::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; RNG => rng::InterruptHandler; }); @@ -39,18 +37,17 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} // Create the driver, from the HAL. let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); @@ -63,12 +60,6 @@ async fn main(spawner: Spawner) { config.max_power = 100; config.max_packet_size_0 = 64; - // Required for Windows support. - config.composite_with_iads = true; - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - // Create embassy-usb DeviceBuilder using the driver and config. static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); @@ -116,10 +107,9 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell>> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf52840/src/bin/usb_hid_keyboard.rs b/examples/nrf52840/src/bin/usb_hid_keyboard.rs index e33ee5866..5a9dc90a2 100644 --- a/examples/nrf52840/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf52840/src/bin/usb_hid_keyboard.rs @@ -1,7 +1,6 @@ #![no_std] #![no_main] -use core::mem; use core::sync::atomic::{AtomicBool, Ordering}; use defmt::*; @@ -22,7 +21,7 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { USBD => usb::InterruptHandler; - POWER_CLOCK => usb::vbus_detect::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; }); static SUSPENDED: AtomicBool = AtomicBool::new(false); @@ -30,11 +29,10 @@ static SUSPENDED: AtomicBool = AtomicBool::new(false); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} // Create the driver, from the HAL. let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); diff --git a/examples/nrf52840/src/bin/usb_hid_mouse.rs b/examples/nrf52840/src/bin/usb_hid_mouse.rs index 8076ac283..80cda70e3 100644 --- a/examples/nrf52840/src/bin/usb_hid_mouse.rs +++ b/examples/nrf52840/src/bin/usb_hid_mouse.rs @@ -1,8 +1,6 @@ #![no_std] #![no_main] -use core::mem; - use defmt::*; use embassy_executor::Spawner; use embassy_futures::join::join; @@ -18,17 +16,16 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { USBD => usb::InterruptHandler; - POWER_CLOCK => usb::vbus_detect::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; }); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} // Create the driver, from the HAL. let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); diff --git a/examples/nrf52840/src/bin/usb_serial.rs b/examples/nrf52840/src/bin/usb_serial.rs index 02048e692..8d05df791 100644 --- a/examples/nrf52840/src/bin/usb_serial.rs +++ b/examples/nrf52840/src/bin/usb_serial.rs @@ -1,8 +1,6 @@ #![no_std] #![no_main] -use core::mem; - use defmt::{info, panic}; use embassy_executor::Spawner; use embassy_futures::join::join; @@ -16,17 +14,16 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { USBD => usb::InterruptHandler; - POWER_CLOCK => usb::vbus_detect::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; }); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} // Create the driver, from the HAL. let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); @@ -39,13 +36,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/nrf52840/src/bin/usb_serial_multitask.rs b/examples/nrf52840/src/bin/usb_serial_multitask.rs index 895cca8b9..5e5b4de35 100644 --- a/examples/nrf52840/src/bin/usb_serial_multitask.rs +++ b/examples/nrf52840/src/bin/usb_serial_multitask.rs @@ -1,8 +1,6 @@ #![no_std] #![no_main] -use core::mem; - use defmt::{info, panic, unwrap}; use embassy_executor::Spawner; use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; @@ -16,7 +14,7 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { USBD => usb::InterruptHandler; - POWER_CLOCK => usb::vbus_detect::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; }); type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; @@ -39,11 +37,10 @@ async fn echo_task(mut class: CdcAcmClass<'static, MyDriver>) { #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} // Create the driver, from the HAL. let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); @@ -56,13 +53,6 @@ async fn main(spawner: Spawner) { 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; - static STATE: StaticCell = StaticCell::new(); let state = STATE.init(State::new()); diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs index c6675a3d3..8a20ce673 100644 --- a/examples/nrf52840/src/bin/usb_serial_winusb.rs +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -1,8 +1,6 @@ #![no_std] #![no_main] -use core::mem; - use defmt::{info, panic}; use embassy_executor::Spawner; use embassy_futures::join::join; @@ -18,7 +16,7 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { USBD => usb::InterruptHandler; - POWER_CLOCK => usb::vbus_detect::InterruptHandler; + CLOCK_POWER => usb::vbus_detect::InterruptHandler; }); // This is a randomly generated GUID to allow clients on Windows to find our device @@ -27,11 +25,10 @@ const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321 #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} + pac::CLOCK.tasks_hfclkstart().write_value(1); + while pac::CLOCK.events_hfclkstarted().read() != 1 {} // Create the driver, from the HAL. let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); @@ -44,13 +41,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/nrf52840/src/bin/wdt.rs b/examples/nrf52840/src/bin/wdt.rs index ede88cc26..0d9ee3cf8 100644 --- a/examples/nrf52840/src/bin/wdt.rs +++ b/examples/nrf52840/src/bin/wdt.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_nrf::gpio::{Input, Pull}; -use embassy_nrf::wdt::{Config, Watchdog}; +use embassy_nrf::wdt::{Config, HaltConfig, Watchdog}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) { // This is needed for `probe-rs run` to be able to catch the panic message // in the WDT interrupt. The core resets 2 ticks after firing the interrupt. - config.run_during_debug_halt = false; + config.action_during_debug_halt = HaltConfig::PAUSE; let (_wdt, [mut handle]) = match Watchdog::try_new(p.WDT, config) { Ok(x) => x, diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs index a3b69a99b..26eaf485e 100644 --- a/examples/nrf52840/src/bin/wifi_esp_hosted.rs +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -4,7 +4,7 @@ use defmt::{info, unwrap, warn}; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; @@ -36,8 +36,8 @@ async fn wifi_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, hosted::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -90,10 +90,9 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell>> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 0da85be07..dc4fba4fd 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -6,22 +6,21 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embedded-io-async = { version = "0.6.1" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" static_cell = "2" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } -rand = { version = "0.8.4", default-features = false } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } embedded-storage = "0.3.1" usbd-hid = "0.8.1" serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf5340/src/bin/uart.rs b/examples/nrf5340/src/bin/uart.rs index 7b41d7463..7e8b8d418 100644 --- a/examples/nrf5340/src/bin/uart.rs +++ b/examples/nrf5340/src/bin/uart.rs @@ -18,7 +18,7 @@ async fn main(_spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let mut uart = uarte::Uarte::new(p.SERIAL0, Irqs, p.P1_00, p.P1_01, config); + let mut uart = uarte::Uarte::new(p.SERIAL0, p.P1_00, p.P1_01, Irqs, config); info!("uarte initialized!"); diff --git a/examples/nrf54l15/.cargo/config.toml b/examples/nrf54l15/.cargo/config.toml new file mode 100644 index 000000000..443bd7418 --- /dev/null +++ b/examples/nrf54l15/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nrf54l15" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/nrf54l15/Cargo.toml b/examples/nrf54l15/Cargo.toml new file mode 100644 index 000000000..4b229d06d --- /dev/null +++ b/examples/nrf54l15/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf54l15-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "nrf54l15-app-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[profile.release] +debug = 2 diff --git a/examples/nrf54l15/build.rs b/examples/nrf54l15/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/nrf54l15/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/nrf54l15/memory.x b/examples/nrf54l15/memory.x new file mode 100644 index 000000000..1064c8a5c --- /dev/null +++ b/examples/nrf54l15/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1536K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/examples/nrf54l15/src/bin/blinky.rs b/examples/nrf54l15/src/bin/blinky.rs new file mode 100644 index 000000000..71fcc461f --- /dev/null +++ b/examples/nrf54l15/src/bin/blinky.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] + +use defmt::info; +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.P2_09, Level::Low, OutputDrive::Standard); + + loop { + info!("high!"); + led.set_high(); + Timer::after_millis(300).await; + info!("low!"); + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/examples/nrf9151/ns/Cargo.toml b/examples/nrf9151/ns/Cargo.toml index 17fe27b67..a083aa5e7 100644 --- a/examples/nrf9151/ns/Cargo.toml +++ b/examples/nrf9151/ns/Cargo.toml @@ -5,16 +5,16 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.7.0", path = "../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } [profile.release] debug = 2 diff --git a/examples/nrf9151/ns/src/bin/uart.rs b/examples/nrf9151/ns/src/bin/uart.rs index 2220dccfb..6fd377978 100644 --- a/examples/nrf9151/ns/src/bin/uart.rs +++ b/examples/nrf9151/ns/src/bin/uart.rs @@ -7,7 +7,7 @@ use embassy_nrf::{bind_interrupts, peripherals, uarte}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { - SPIM0_SPIS0_TWIM0_TWIS0_UARTE0 => uarte::InterruptHandler; + SERIAL0 => uarte::InterruptHandler; }); #[embassy_executor::main] @@ -17,7 +17,7 @@ async fn main(_spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let mut uart = uarte::Uarte::new(p.SERIAL0, Irqs, p.P0_26, p.P0_27, config); + let mut uart = uarte::Uarte::new(p.SERIAL0, p.P0_26, p.P0_27, Irqs, config); info!("uarte initialized!"); diff --git a/examples/nrf9151/s/Cargo.toml b/examples/nrf9151/s/Cargo.toml index 7253fc4be..ae98631ef 100644 --- a/examples/nrf9151/s/Cargo.toml +++ b/examples/nrf9151/s/Cargo.toml @@ -5,16 +5,16 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.7.0", path = "../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } [profile.release] debug = 2 diff --git a/examples/nrf9160/.cargo/config.toml b/examples/nrf9160/.cargo/config.toml index f64c63966..6072b8595 100644 --- a/examples/nrf9160/.cargo/config.toml +++ b/examples/nrf9160/.cargo/config.toml @@ -1,5 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-rs run --chip nRF9160_xxAA" +# runner = "probe-rs run --chip nRF9160_xxAA" +runner = [ "probe-rs", "run", "--chip=nRF9160_xxAA", "--always-print-stacktrace", "--log-format={t} {[{L}]%bold} {s} {{c} {ff}:{l:1}%dimmed}" ] [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/nrf9160/Cargo.toml b/examples/nrf9160/Cargo.toml index c30b54ebd..25aedf624 100644 --- a/examples/nrf9160/Cargo.toml +++ b/examples/nrf9160/Cargo.toml @@ -5,16 +5,22 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" +heapless = "0.8" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +static_cell = { version = "2" } +embedded-io = "0.6.1" +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } [profile.release] debug = 2 diff --git a/examples/nrf9160/memory.x b/examples/nrf9160/memory.x index 4c7d4ebf0..e33498773 100644 --- a/examples/nrf9160/memory.x +++ b/examples/nrf9160/memory.x @@ -1,5 +1,9 @@ MEMORY { - FLASH : ORIGIN = 0x00000000, LENGTH = 1024K - RAM : ORIGIN = 0x20018000, LENGTH = 160K + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20010000, LENGTH = 192K + IPC : ORIGIN = 0x20000000, LENGTH = 64K } + +PROVIDE(__start_ipc = ORIGIN(IPC)); +PROVIDE(__end_ipc = ORIGIN(IPC) + LENGTH(IPC)); diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs new file mode 100644 index 000000000..a36b14626 --- /dev/null +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -0,0 +1,197 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; +use core::net::IpAddr; +use core::ptr::addr_of_mut; +use core::slice; +use core::str::FromStr; + +use defmt::{info, unwrap, warn}; +use embassy_executor::Spawner; +use embassy_net::{Ipv4Cidr, Stack, StackResources}; +use embassy_net_nrf91::context::Status; +use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; +use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; +use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive}; +use embassy_nrf::uarte::Baudrate; +use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte, Peri}; +use embassy_time::{Duration, Timer}; +use embedded_io_async::Write; +use heapless::Vec; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[interrupt] +fn IPC() { + embassy_net_nrf91::on_ipc_irq(); +} + +bind_interrupts!(struct Irqs { + SERIAL0 => buffered_uarte::InterruptHandler; +}); + +#[embassy_executor::task] +async fn trace_task(mut uart: BufferedUarteTx<'static, peripherals::SERIAL0>, reader: TraceReader<'static>) -> ! { + let mut rx = [0u8; 1024]; + loop { + let n = reader.read(&mut rx[..]).await; + unwrap!(uart.write_all(&rx[..n]).await); + } +} + +#[embassy_executor::task] +async fn modem_task(runner: Runner<'static>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_nrf91::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn control_task( + control: &'static context::Control<'static>, + config: context::Config<'static>, + stack: Stack<'static>, +) { + unwrap!(control.configure(&config).await); + unwrap!( + control + .run(|status| { + stack.set_config_v4(status_to_config(status)); + }) + .await + ); +} + +fn status_to_config(status: &Status) -> embassy_net::ConfigV4 { + let Some(IpAddr::V4(addr)) = status.ip else { + panic!("Unexpected IP address"); + }; + + let gateway = match status.gateway { + Some(IpAddr::V4(addr)) => Some(addr), + _ => None, + }; + + let mut dns_servers = Vec::new(); + for dns in status.dns.iter() { + if let IpAddr::V4(ip) = dns { + unwrap!(dns_servers.push(*ip)); + } + } + + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(addr, 32), + gateway, + dns_servers, + }) +} + +#[embassy_executor::task] +async fn blink_task(pin: Peri<'static, AnyPin>) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + loop { + led.set_high(); + Timer::after_millis(1000).await; + led.set_low(); + Timer::after_millis(1000).await; + } +} + +extern "C" { + static __start_ipc: u8; + static __end_ipc: u8; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Hello World!"); + + unwrap!(spawner.spawn(blink_task(p.P0_02.into()))); + + let ipc_mem = unsafe { + let ipc_start = &__start_ipc as *const u8 as *mut MaybeUninit; + let ipc_end = &__end_ipc as *const u8 as *mut MaybeUninit; + let ipc_len = ipc_end.offset_from(ipc_start) as usize; + slice::from_raw_parts_mut(ipc_start, ipc_len) + }; + + static mut TRACE_BUF: [u8; 4096] = [0u8; 4096]; + let mut config = uarte::Config::default(); + config.baudrate = Baudrate::BAUD1M; + let uart = BufferedUarteTx::new( + //let trace_uart = BufferedUarteTx::new( + unsafe { peripherals::SERIAL0::steal() }, + unsafe { peripherals::P0_01::steal() }, + Irqs, + //unsafe { peripherals::P0_14::steal() }, + config, + unsafe { &mut *addr_of_mut!(TRACE_BUF) }, + ); + + static STATE: StaticCell = StaticCell::new(); + static TRACE: StaticCell = StaticCell::new(); + let (device, control, runner, tracer) = + embassy_net_nrf91::new_with_trace(STATE.init(State::new()), ipc_mem, TRACE.init(TraceBuffer::new())).await; + unwrap!(spawner.spawn(modem_task(runner))); + unwrap!(spawner.spawn(trace_task(uart, tracer))); + + let config = embassy_net::Config::default(); + + // Generate "random" seed. nRF91 has no RNG, TODO figure out something... + let seed = 123456; + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::<2>::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + static CONTROL: StaticCell> = StaticCell::new(); + let control = CONTROL.init(context::Control::new(control, 0).await); + + unwrap!(spawner.spawn(control_task( + control, + context::Config { + apn: b"iot.nat.es", + auth_prot: context::AuthProt::Pap, + auth: Some((b"orange", b"orange")), + pin: None, + }, + stack + ))); + + stack.wait_config_up().await; + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap(); + if let Err(e) = socket.connect((host_addr, 4242)).await { + warn!("connect error: {:?}", e); + Timer::after_secs(10).await; + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + + let msg = b"Hello world!\n"; + for _ in 0..10 { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after_secs(1).await; + } + Timer::after_secs(4).await; + } +} diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 83d5792b6..c8a132a5e 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -6,43 +6,43 @@ license = "MIT OR Apache-2.0" [dependencies] -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } -embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.4.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "icmp", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns", "proto-ipv4", "proto-ipv6", "multicast"] } +embassy-net-wiznet = { version = "0.2.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } -cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } -cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } +embassy-usb-logger = { version = "0.4.0", path = "../../embassy-usb-logger" } +cyw43 = { version = "0.3.0", path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { version = "0.4.0", path = "../../cyw43-pio", features = ["defmt"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" fixed = "1.23.1" fixed-macro = "1.2" # for web request example -reqwless = { version = "0.12.0", features = ["defmt",]} +reqwless = { version = "0.13.0", features = ["defmt"] } serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde-json-core = "0.5.1" # for assign resources example -assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "bd22cb7a92031fb16f74a5da42469d466c33383e" } #cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m = { version = "0.7.6", features = ["inline-asm"] } cortex-m-rt = "0.7.0" critical-section = "1.1" -panic-probe = { version = "0.3", features = ["print-defmt"] } -display-interface-spi = "0.4.1" -embedded-graphics = "0.7.1" -st7789 = "0.6.1" -display-interface = "0.4.1" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +display-interface-spi = "0.5.0" +embedded-graphics = "0.8.1" +mipidsi = "0.8.0" +display-interface = "0.5.0" byte-slice-cast = { version = "1.2.0", default-features = false } -smart-leds = "0.3.0" +smart-leds = "0.4.0" heapless = "0.8" usbd-hid = "0.8.1" @@ -54,14 +54,9 @@ embedded-storage = { version = "0.3" } static_cell = "2.1" portable-atomic = { version = "1.5", features = ["critical-section"] } log = "0.4" -pio-proc = "0.2" -pio = "0.2.1" -rand = { version = "0.8.5", default-features = false } +rand = { version = "0.9.0", default-features = false } embedded-sdmmc = "0.7.0" -bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } -trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } - [profile.release] debug = 2 lto = true @@ -71,13 +66,3 @@ opt-level = 'z' debug = 2 lto = true opt-level = "z" - -[patch.crates-io] -trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } -bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3" } -embassy-executor = { path = "../../embassy-executor" } -embassy-sync = { path = "../../embassy-sync" } -embassy-futures = { path = "../../embassy-futures" } -embassy-time = { path = "../../embassy-time" } -embassy-time-driver = { path = "../../embassy-time-driver" } -embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs index 1bb7c2249..015915586 100644 --- a/examples/rp/src/bin/adc.rs +++ b/examples/rp/src/bin/adc.rs @@ -12,9 +12,12 @@ use embassy_rp::gpio::Pull; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -bind_interrupts!(struct Irqs { - ADC_IRQ_FIFO => InterruptHandler; -}); +bind_interrupts!( + /// Binds the ADC interrupts. + struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; + } +); #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rp/src/bin/adc_dma.rs b/examples/rp/src/bin/adc_dma.rs index f755cf5bf..b42c13fde 100644 --- a/examples/rp/src/bin/adc_dma.rs +++ b/examples/rp/src/bin/adc_dma.rs @@ -38,13 +38,13 @@ async fn main(_spawner: Spawner) { // Read 100 samples from a single channel let mut buf = [0_u16; BLOCK_SIZE]; let div = 479; // 100kHz sample rate (48Mhz / 100kHz - 1) - adc.read_many(&mut pin, &mut buf, div, &mut dma).await.unwrap(); + adc.read_many(&mut pin, &mut buf, div, dma.reborrow()).await.unwrap(); info!("single: {:?} ...etc", buf[..8]); // Read 100 samples from 4 channels interleaved let mut buf = [0_u16; { BLOCK_SIZE * NUM_CHANNELS }]; let div = 119; // 100kHz sample rate (48Mhz / 100kHz * 4ch - 1) - adc.read_many_multichannel(&mut pins, &mut buf, div, &mut dma) + adc.read_many_multichannel(&mut pins, &mut buf, div, dma.reborrow()) .await .unwrap(); info!("multi: {:?} ...etc", buf[..NUM_CHANNELS * 2]); diff --git a/examples/rp/src/bin/assign_resources.rs b/examples/rp/src/bin/assign_resources.rs index ff6eff4a2..341f54d22 100644 --- a/examples/rp/src/bin/assign_resources.rs +++ b/examples/rp/src/bin/assign_resources.rs @@ -16,6 +16,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{self, PIN_20, PIN_21}; +use embassy_rp::Peri; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -38,7 +39,11 @@ async fn main(spawner: Spawner) { // 1) Assigning a resource to a task by passing parts of the peripherals. #[embassy_executor::task] -async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) { +async fn double_blinky_manually_assigned( + _spawner: Spawner, + pin_20: Peri<'static, PIN_20>, + pin_21: Peri<'static, PIN_21>, +) { let mut led_20 = Output::new(pin_20, Level::Low); let mut led_21 = Output::new(pin_21, Level::High); diff --git a/examples/rp/src/bin/blinky_two_channels.rs b/examples/rp/src/bin/blinky_two_channels.rs index b2eec2a21..51e139e94 100644 --- a/examples/rp/src/bin/blinky_two_channels.rs +++ b/examples/rp/src/bin/blinky_two_channels.rs @@ -11,7 +11,7 @@ use embassy_rp::gpio; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::{Channel, Sender}; use embassy_time::{Duration, Ticker}; -use gpio::{AnyPin, Level, Output}; +use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; enum LedState { @@ -22,7 +22,7 @@ static CHANNEL: Channel = Channel::new(); #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High); + let mut led = Output::new(p.PIN_25, Level::High); let dt = 100 * 1_000_000; let k = 1.003; diff --git a/examples/rp/src/bin/blinky_two_tasks.rs b/examples/rp/src/bin/blinky_two_tasks.rs index a57b513d6..67a9108c0 100644 --- a/examples/rp/src/bin/blinky_two_tasks.rs +++ b/examples/rp/src/bin/blinky_two_tasks.rs @@ -11,7 +11,7 @@ use embassy_rp::gpio; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Ticker}; -use gpio::{AnyPin, Level, Output}; +use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; type LedType = Mutex>>; @@ -21,7 +21,7 @@ static LED: LedType = Mutex::new(None); async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); // set the content of the global LED reference to the real LED pin - let led = Output::new(AnyPin::from(p.PIN_25), Level::High); + let led = Output::new(p.PIN_25, Level::High); // inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the // Mutex is released { diff --git a/examples/rp/src/bin/bluetooth.rs b/examples/rp/src/bin/bluetooth.rs deleted file mode 100644 index 901521b60..000000000 --- a/examples/rp/src/bin/bluetooth.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! This example test the RP Pico W on board LED. -//! -//! It does not work with the RP Pico board. See blinky.rs. - -#![no_std] -#![no_main] - -use bt_hci::controller::ExternalController; -use cyw43_pio::PioSpi; -use defmt::*; -use embassy_executor::Spawner; -use embassy_futures::join::join3; -use embassy_rp::bind_interrupts; -use embassy_rp::gpio::{Level, Output}; -use embassy_rp::peripherals::{DMA_CH0, PIO0}; -use embassy_rp::pio::{InterruptHandler, Pio}; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_time::{Duration, Timer}; -use static_cell::StaticCell; -use trouble_host::advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}; -use trouble_host::attribute::{AttributeTable, CharacteristicProp, Service, Uuid}; -use trouble_host::gatt::GattEvent; -use trouble_host::{Address, BleHost, BleHostResources, PacketQos}; -use {defmt_rtt as _, embassy_time as _, panic_probe as _}; - -bind_interrupts!(struct Irqs { - PIO0_IRQ_0 => InterruptHandler; -}); - -#[embassy_executor::task] -async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { - runner.run().await -} - -#[embassy_executor::main] -async fn main(spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); - let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); - let btfw = include_bytes!("../../../../cyw43-firmware/43439A0_btfw.bin"); - - // To make flashing faster for development, you may want to flash the firmwares independently - // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: - // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 - // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 - //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; - //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; - - let pwr = Output::new(p.PIN_23, Level::Low); - let cs = Output::new(p.PIN_25, Level::High); - let mut pio = Pio::new(p.PIO0, Irqs); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); - - static STATE: StaticCell = StaticCell::new(); - let state = STATE.init(cyw43::State::new()); - let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; - unwrap!(spawner.spawn(cyw43_task(runner))); - control.init(clm).await; - - let controller: ExternalController<_, 10> = ExternalController::new(bt_device); - static HOST_RESOURCES: StaticCell> = StaticCell::new(); - let host_resources = HOST_RESOURCES.init(BleHostResources::new(PacketQos::None)); - - let mut ble: BleHost<'_, _> = BleHost::new(controller, host_resources); - - ble.set_random_address(Address::random([0xff, 0x9f, 0x1a, 0x05, 0xe4, 0xff])); - let mut table: AttributeTable<'_, NoopRawMutex, 10> = AttributeTable::new(); - - // Generic Access Service (mandatory) - let id = b"Pico W Bluetooth"; - let appearance = [0x80, 0x07]; - let mut bat_level = [0; 1]; - let handle = { - let mut svc = table.add_service(Service::new(0x1800)); - let _ = svc.add_characteristic_ro(0x2a00, id); - let _ = svc.add_characteristic_ro(0x2a01, &appearance[..]); - svc.build(); - - // Generic attribute service (mandatory) - table.add_service(Service::new(0x1801)); - - // Battery service - let mut svc = table.add_service(Service::new(0x180f)); - - svc.add_characteristic( - 0x2a19, - &[CharacteristicProp::Read, CharacteristicProp::Notify], - &mut bat_level, - ) - .build() - }; - - let mut adv_data = [0; 31]; - AdStructure::encode_slice( - &[ - AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), - AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), - AdStructure::CompleteLocalName(b"Pico W Bluetooth"), - ], - &mut adv_data[..], - ) - .unwrap(); - - let server = ble.gatt_server(&table); - - info!("Starting advertising and GATT service"); - let _ = join3( - ble.run(), - async { - loop { - match server.next().await { - Ok(GattEvent::Write { handle, connection: _ }) => { - let _ = table.get(handle, |value| { - info!("Write event. Value written: {:?}", value); - }); - } - Ok(GattEvent::Read { .. }) => { - info!("Read event"); - } - Err(e) => { - error!("Error processing GATT events: {:?}", e); - } - } - } - }, - async { - let mut advertiser = ble - .advertise( - &Default::default(), - Advertisement::ConnectableScannableUndirected { - adv_data: &adv_data[..], - scan_data: &[], - }, - ) - .await - .unwrap(); - let conn = advertiser.accept().await.unwrap(); - // Keep connection alive - let mut tick: u8 = 0; - loop { - Timer::after(Duration::from_secs(10)).await; - tick += 1; - server.notify(handle, &conn, &[tick]).await.unwrap(); - } - }, - ) - .await; -} diff --git a/examples/rp/src/bin/ethernet_w5500_icmp.rs b/examples/rp/src/bin/ethernet_w5500_icmp.rs new file mode 100644 index 000000000..e434b3bbc --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_icmp.rs @@ -0,0 +1,143 @@ +//! This example implements an echo (ping) with an ICMP Socket and using defmt to report the results. +//! +//! Although there is a better way to execute pings using the child module ping of the icmp module, +//! this example allows for other icmp messages like `Destination unreachable` to be sent aswell. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico) board. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::icmp::{ChecksumCapabilities, IcmpEndpoint, IcmpSocket, Icmpv4Packet, Icmpv4Repr, PacketMetadata}; +use embassy_net::{Stack, StackResources}; +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::SPI0; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Instant, Timer}; +use embedded_hal_bus::spi::ExclusiveDevice; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type ExclusiveSpiDevice = ExclusiveDevice, Output<'static>, Delay>; + +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, W5500, ExclusiveSpiDevice, Input<'static>, Output<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Then we can use it! + let mut rx_buffer = [0; 256]; + let mut tx_buffer = [0; 256]; + let mut rx_meta = [PacketMetadata::EMPTY]; + let mut tx_meta = [PacketMetadata::EMPTY]; + + // Identifier used for the ICMP socket + let ident = 42; + + // Create and bind the socket + let mut socket = IcmpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + socket.bind(IcmpEndpoint::Ident(ident)).unwrap(); + + // Create the repr for the packet + let icmp_repr = Icmpv4Repr::EchoRequest { + ident, + seq_no: 0, + data: b"Hello, icmp!", + }; + + // Send the packet and store the starting instant to mesure latency later + let start = socket + .send_to_with(icmp_repr.buffer_len(), cfg.gateway.unwrap(), |buf| { + // Create and populate the packet buffer allocated by `send_to_with` + let mut icmp_packet = Icmpv4Packet::new_unchecked(buf); + icmp_repr.emit(&mut icmp_packet, &ChecksumCapabilities::default()); + Instant::now() // Return the instant where the packet was sent + }) + .await + .unwrap(); + + // Recieve and log the data of the reply + socket + .recv_from_with(|(buf, addr)| { + let packet = Icmpv4Packet::new_checked(buf).unwrap(); + info!( + "Recieved {:?} from {} in {}ms", + packet.data(), + addr, + start.elapsed().as_millis() + ); + }) + .await + .unwrap(); + + loop { + Timer::after_secs(10).await; + } +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_icmp_ping.rs b/examples/rp/src/bin/ethernet_w5500_icmp_ping.rs new file mode 100644 index 000000000..0ec594fd5 --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_icmp_ping.rs @@ -0,0 +1,134 @@ +//! This example implements a LAN ping scan with the ping utilities in the icmp module of embassy-net. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico) board. + +#![no_std] +#![no_main] + +use core::net::Ipv4Addr; +use core::ops::Not; +use core::str::FromStr; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::icmp::ping::{PingManager, PingParams}; +use embassy_net::icmp::PacketMetadata; +use embassy_net::{Ipv4Cidr, Stack, StackResources}; +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::SPI0; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Duration}; +use embedded_hal_bus::spi::ExclusiveDevice; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +type ExclusiveSpiDevice = ExclusiveDevice, Output<'static>, Delay>; + +#[embassy_executor::task] +async fn ethernet_task(runner: Runner<'static, W5500, ExclusiveSpiDevice, Input<'static>, Output<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + static STATE: StaticCell> = StaticCell::new(); + let state = STATE.init(State::<8, 8>::new()); + let (device, runner) = embassy_net_wiznet::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await + .unwrap(); + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + RESOURCES.init(StackResources::new()), + seed, + ); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + let gateway = cfg.gateway.unwrap(); + let mask = cfg.address.netmask(); + let lower_bound = (gateway.to_bits() & mask.to_bits()) + 1; + let upper_bound = gateway.to_bits() | mask.to_bits().not(); + let addr_range = lower_bound..=upper_bound; + + // Then we can use it! + let mut rx_buffer = [0; 256]; + let mut tx_buffer = [0; 256]; + let mut rx_meta = [PacketMetadata::EMPTY]; + let mut tx_meta = [PacketMetadata::EMPTY]; + + // Create the ping manager instance + let mut ping_manager = PingManager::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + let addr = "192.168.8.1"; // Address to ping to + // Create the PingParams with the target address + let mut ping_params = PingParams::new(Ipv4Addr::from_str(addr).unwrap()); + // (optional) Set custom properties of the ping + ping_params.set_payload(b"Hello, Ping!"); // custom payload + ping_params.set_count(1); // ping 1 times per ping call + ping_params.set_timeout(Duration::from_millis(500)); // wait .5 seconds instead of 4 + + info!("Online hosts in {}:", Ipv4Cidr::from_netmask(gateway, mask).unwrap()); + let mut total_online_hosts = 0u32; + for addr in addr_range { + let ip_addr = Ipv4Addr::from_bits(addr); + // Set the target address in the ping params + ping_params.set_target(ip_addr); + // Execute the ping with the given parameters and wait for the reply + match ping_manager.ping(&ping_params).await { + Ok(time) => { + info!("{} is online\n- latency: {}ms\n", ip_addr, time.as_millis()); + total_online_hosts += 1; + } + _ => continue, + } + } + info!("Ping scan complete, total online hosts: {}", total_online_hosts); +} + +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index aaa035a72..27e2f3c30 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -1,6 +1,6 @@ //! This example shows how you can allow multiple simultaneous TCP connections, by having multiple sockets listening on the same port. //! -//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico) board. #![no_std] #![no_main] @@ -18,7 +18,6 @@ use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_io_async::Write; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -36,8 +35,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -71,17 +70,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -89,12 +87,12 @@ async fn main(spawner: Spawner) { info!("IP address: {:?}", local_addr); // Create two sockets listening to the same port, to handle simultaneous connections - unwrap!(spawner.spawn(listen_task(&stack, 0, 1234))); - unwrap!(spawner.spawn(listen_task(&stack, 1, 1234))); + unwrap!(spawner.spawn(listen_task(stack, 0, 1234))); + unwrap!(spawner.spawn(listen_task(stack, 1, 1234))); } #[embassy_executor::task(pool_size = 2)] -async fn listen_task(stack: &'static Stack>, id: u8, port: u16) { +async fn listen_task(stack: Stack<'static>, id: u8, port: u16) { let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; let mut buf = [0; 4096]; @@ -131,7 +129,7 @@ async fn listen_task(stack: &'static Stack>, id: u8, port: u16) } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index 8e96a114c..ba82f2a60 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -1,6 +1,6 @@ //! This example implements a TCP client that attempts to connect to a host on port 1234 and send it some data once per second. //! -//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico) board. #![no_std] #![no_main] @@ -20,7 +20,6 @@ use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_io_async::Write; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -38,8 +37,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -74,17 +73,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -119,7 +117,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 40736bf3c..5c56dcafa 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -1,7 +1,7 @@ //! This example implements a TCP echo server on port 1234 and using DHCP. //! Send it some data, you should see it echoed back and printed in the console. //! -//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico) board. #![no_std] #![no_main] @@ -19,7 +19,6 @@ use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration}; use embedded_hal_bus::spi::ExclusiveDevice; use embedded_io_async::Write; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -37,8 +36,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -73,17 +72,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -128,7 +126,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index c79f01538..c5fc8de1d 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -1,6 +1,6 @@ //! This example implements a UDP server listening on port 1234 and echoing back the data. //! -//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico) board. #![no_std] #![no_main] @@ -18,7 +18,6 @@ use embassy_rp::peripherals::SPI0; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::Delay; use embedded_hal_bus::spi::ExclusiveDevice; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -36,8 +35,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -71,17 +70,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -108,7 +106,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/i2c_slave.rs b/examples/rp/src/bin/i2c_slave.rs index 9fffb4646..08f31001b 100644 --- a/examples/rp/src/bin/i2c_slave.rs +++ b/examples/rp/src/bin/i2c_slave.rs @@ -99,19 +99,19 @@ async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); info!("Hello World!"); - let d_sda = p.PIN_3; - let d_scl = p.PIN_2; + let d_sda = p.PIN_2; + let d_scl = p.PIN_3; 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); + let device = i2c_slave::I2cSlave::new(p.I2C1, d_scl, d_sda, Irqs, config); unwrap!(spawner.spawn(device_task(device))); - let c_sda = p.PIN_1; - let c_scl = p.PIN_0; + let c_sda = p.PIN_0; + let c_scl = p.PIN_1; let mut config = i2c::Config::default(); config.frequency = 1_000_000; - let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); + let controller = i2c::I2c::new_async(p.I2C0, c_scl, c_sda, Irqs, config); unwrap!(spawner.spawn(controller_task(controller))); } diff --git a/examples/rp/src/bin/interrupt.rs b/examples/rp/src/bin/interrupt.rs index 5b9d7027e..787cdc112 100644 --- a/examples/rp/src/bin/interrupt.rs +++ b/examples/rp/src/bin/interrupt.rs @@ -32,7 +32,6 @@ static ADC_VALUES: Channel = Channel::new(); #[embassy_executor::main] async fn main(spawner: Spawner) { - embassy_rp::pac::SIO.spinlock(31).write_value(1); let p = embassy_rp::init(Default::default()); let adc = Adc::new_blocking(p.ADC, Default::default()); diff --git a/examples/rp/src/bin/orchestrate_tasks.rs b/examples/rp/src/bin/orchestrate_tasks.rs new file mode 100644 index 000000000..c35679251 --- /dev/null +++ b/examples/rp/src/bin/orchestrate_tasks.rs @@ -0,0 +1,318 @@ +//! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system. +//! +//! The system consists of several tasks: +//! - Three tasks that generate random numbers at different intervals (simulating i.e. sensor readings) +//! - A task that monitors USB power connection (hardware event handling) +//! - A task that reads system voltage (ADC sampling) +//! - A consumer task that processes all this information +//! +//! The system maintains state in a single place, wrapped in a Mutex. +//! +//! We demonstrate how to: +//! - use a mutex to maintain shared state between tasks +//! - use a channel to send events between tasks +//! - use an orchestrator task to coordinate tasks and handle state transitions +//! - use signals to notify about state changes and terminate tasks + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::{bind_interrupts, peripherals, Peri}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_sync::{channel, signal}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +// Hardware resource assignment. See other examples for different ways of doing this. +assign_resources! { + vsys: Vsys { + adc: ADC, + pin_29: PIN_29, + }, + vbus: Vbus { + pin_24: PIN_24, + }, +} + +// Interrupt binding - required for hardware peripherals like ADC +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +/// Events that worker tasks send to the orchestrator +enum Events { + UsbPowered(bool), // USB connection state changed + VsysVoltage(f32), // New voltage reading + FirstRandomSeed(u32), // Random number from 30s timer + SecondRandomSeed(u32), // Random number from 60s timer + ThirdRandomSeed(u32), // Random number from 90s timer + ResetFirstRandomSeed, // Signal to reset the first counter +} + +/// Commands that can control task behavior. +/// Currently only used to stop tasks, but could be extended for other controls. +enum Commands { + /// Signals a task to stop execution + Stop, +} + +/// The central state of our system, shared between tasks. +#[derive(Clone, Format)] +struct State { + usb_powered: bool, + vsys_voltage: f32, + first_random_seed: u32, + second_random_seed: u32, + third_random_seed: u32, + first_random_seed_task_running: bool, + times_we_got_first_random_seed: u8, + maximum_times_we_want_first_random_seed: u8, +} + +/// A formatted view of the system status, used for logging. Used for the below `get_system_summary` fn. +#[derive(Format)] +struct SystemStatus { + power_source: &'static str, + voltage: f32, +} + +impl State { + const fn new() -> Self { + Self { + usb_powered: false, + vsys_voltage: 0.0, + first_random_seed: 0, + second_random_seed: 0, + third_random_seed: 0, + first_random_seed_task_running: false, + times_we_got_first_random_seed: 0, + maximum_times_we_want_first_random_seed: 3, + } + } + + /// Returns a formatted summary of power state and voltage. + /// Shows how to create methods that work with shared state. + fn get_system_summary(&self) -> SystemStatus { + SystemStatus { + power_source: if self.usb_powered { + "USB powered" + } else { + "Battery powered" + }, + voltage: self.vsys_voltage, + } + } +} + +/// The shared state protected by a mutex +static SYSTEM_STATE: Mutex = Mutex::new(State::new()); + +/// Channel for events from worker tasks to the orchestrator +static EVENT_CHANNEL: channel::Channel = channel::Channel::new(); + +/// Signal used to stop the first random number task +static STOP_FIRST_RANDOM_SIGNAL: signal::Signal = signal::Signal::new(); + +/// Signal for notifying about state changes +static STATE_CHANGED: signal::Signal = signal::Signal::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let r = split_resources! {p}; + + spawner.spawn(orchestrate(spawner)).unwrap(); + spawner.spawn(random_60s(spawner)).unwrap(); + spawner.spawn(random_90s(spawner)).unwrap(); + // `random_30s` is not spawned here, butin the orchestrate task depending on state + spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); + spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); + spawner.spawn(consumer(spawner)).unwrap(); +} + +/// Main task that processes all events and updates system state. +#[embassy_executor::task] +async fn orchestrate(spawner: Spawner) { + let receiver = EVENT_CHANNEL.receiver(); + + loop { + // Do nothing until we receive any event + let event = receiver.receive().await; + + // Scope in which we want to lock the system state. As an alternative we could also call `drop` on the state + { + let mut state = SYSTEM_STATE.lock().await; + + match event { + Events::UsbPowered(usb_powered) => { + state.usb_powered = usb_powered; + info!("Usb powered: {}", usb_powered); + info!("System summary: {}", state.get_system_summary()); + } + Events::VsysVoltage(voltage) => { + state.vsys_voltage = voltage; + info!("Vsys voltage: {}", voltage); + } + Events::FirstRandomSeed(seed) => { + state.first_random_seed = seed; + state.times_we_got_first_random_seed += 1; + info!( + "First random seed: {}, and that was iteration {} of receiving this.", + seed, &state.times_we_got_first_random_seed + ); + } + Events::SecondRandomSeed(seed) => { + state.second_random_seed = seed; + info!("Second random seed: {}", seed); + } + Events::ThirdRandomSeed(seed) => { + state.third_random_seed = seed; + info!("Third random seed: {}", seed); + } + Events::ResetFirstRandomSeed => { + state.times_we_got_first_random_seed = 0; + state.first_random_seed = 0; + info!("Resetting the first random seed counter"); + } + } + + // Handle task orchestration based on state + // Just placed as an example here, could be hooked into the event system, puton a timer, ... + match state.times_we_got_first_random_seed { + max if max == state.maximum_times_we_want_first_random_seed => { + info!("Stopping the first random signal task"); + STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); + EVENT_CHANNEL.sender().send(Events::ResetFirstRandomSeed).await; + } + 0 => { + let respawn_first_random_seed_task = !state.first_random_seed_task_running; + // Deliberately dropping the Mutex lock here to release it before a lengthy operation + drop(state); + if respawn_first_random_seed_task { + info!("(Re)-Starting the first random signal task"); + spawner.spawn(random_30s(spawner)).unwrap(); + } + } + _ => {} + } + } + + STATE_CHANGED.signal(()); + } +} + +/// Task that monitors state changes and logs system status. +#[embassy_executor::task] +async fn consumer(_spawner: Spawner) { + loop { + // Wait for state change notification + STATE_CHANGED.wait().await; + + let state = SYSTEM_STATE.lock().await; + info!( + "State update - {} | Seeds - First: {} (count: {}/{}, running: {}), Second: {}, Third: {}", + state.get_system_summary(), + state.first_random_seed, + state.times_we_got_first_random_seed, + state.maximum_times_we_want_first_random_seed, + state.first_random_seed_task_running, + state.second_random_seed, + state.third_random_seed + ); + } +} + +/// Task that generates random numbers every 30 seconds until stopped. +/// Shows how to handle both timer events and stop signals. +/// As an example of some routine we want to be on or off depending on other needs. +#[embassy_executor::task] +async fn random_30s(_spawner: Spawner) { + { + let mut state = SYSTEM_STATE.lock().await; + state.first_random_seed_task_running = true; + } + + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + + loop { + // Wait for either 30s timer or stop signal (like select() in Go) + match select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await { + Either::First(_) => { + info!("30s are up, generating random number"); + let random_number = rng.next_u32(); + sender.send(Events::FirstRandomSeed(random_number)).await; + } + Either::Second(_) => { + info!("Received signal to stop, goodbye!"); + + let mut state = SYSTEM_STATE.lock().await; + state.first_random_seed_task_running = false; + + break; + } + } + } +} + +/// Task that generates random numbers every 60 seconds. As an example of some routine. +#[embassy_executor::task] +async fn random_60s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + + loop { + Timer::after(Duration::from_secs(60)).await; + let random_number = rng.next_u32(); + sender.send(Events::SecondRandomSeed(random_number)).await; + } +} + +/// Task that generates random numbers every 90 seconds. . As an example of some routine. +#[embassy_executor::task] +async fn random_90s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + + loop { + Timer::after(Duration::from_secs(90)).await; + let random_number = rng.next_u32(); + sender.send(Events::ThirdRandomSeed(random_number)).await; + } +} + +/// Task that monitors USB power connection. As an example of some Interrupt somewhere. +#[embassy_executor::task] +pub async fn usb_power(_spawner: Spawner, r: Vbus) { + let mut vbus_in = Input::new(r.pin_24, Pull::None); + let sender = EVENT_CHANNEL.sender(); + + loop { + sender.send(Events::UsbPowered(vbus_in.is_high())).await; + vbus_in.wait_for_any_edge().await; + } +} + +/// Task that reads system voltage through ADC. As an example of some continuous sensor reading. +#[embassy_executor::task] +pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) { + let mut adc = Adc::new(r.adc, Irqs, Config::default()); + let vsys_in = r.pin_29; + let mut channel = Channel::new_pin(vsys_in, Pull::None); + let sender = EVENT_CHANNEL.sender(); + + loop { + Timer::after(Duration::from_secs(30)).await; + let adc_value = adc.read(&mut channel).await.unwrap(); + let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0; + sender.send(Events::VsysVoltage(voltage)).await; + } +} diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs new file mode 100644 index 000000000..83b17308b --- /dev/null +++ b/examples/rp/src/bin/overclock.rs @@ -0,0 +1,64 @@ +//! # Overclocking the RP2040 to 200 MHz +//! +//! This example demonstrates how to configure the RP2040 to run at 200 MHz. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Set up for clock frequency of 200 MHz, setting all necessary defaults. + let config = Config::new(ClockConfig::system_freq(200_000_000).unwrap()); + + // Initialize the peripherals + let p = embassy_rp::init(config); + + // Show CPU frequency for verification + let sys_freq = clk_sys_freq(); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); + // Show core voltage for verification + let core_voltage = core_voltage().unwrap(); + info!("Core voltage: {}", core_voltage); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = Instant::now() - start; + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + counter, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/examples/rp/src/bin/overclock_manual.rs b/examples/rp/src/bin/overclock_manual.rs new file mode 100644 index 000000000..dea5cfb3c --- /dev/null +++ b/examples/rp/src/bin/overclock_manual.rs @@ -0,0 +1,81 @@ +//! # Overclocking the RP2040 to 200 MHz manually +//! +//! This example demonstrates how to manually configure the RP2040 to run at 200 MHz. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig, CoreVoltage, PllConfig}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +/// Configure the RP2040 for 200 MHz operation by manually specifying the PLL settings. +fn configure_manual_overclock() -> Config { + // Set the PLL configuration manually, starting from default values + let mut config = Config::default(); + + // Set the system clock to 200 MHz + config.clocks = ClockConfig::manual_pll( + 12_000_000, // Crystal frequency, 12 MHz is common. If using custom, set to your value. + PllConfig { + refdiv: 1, // Reference divider + fbdiv: 100, // Feedback divider + post_div1: 3, // Post divider 1 + post_div2: 2, // Post divider 2 + }, + CoreVoltage::V1_15, // Core voltage, should be set to V1_15 for 200 MHz + ); + + config +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Initialize with our manual overclock configuration + let p = embassy_rp::init(configure_manual_overclock()); + + // Show CPU frequency for verification + let sys_freq = clk_sys_freq(); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); + // Show core voltage for verification + let core_voltage = core_voltage().unwrap(); + info!("Core voltage: {}", core_voltage); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = Instant::now() - start; + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + counter, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/examples/rp/src/bin/pio_async.rs b/examples/rp/src/bin/pio_async.rs index ee248591b..bf6dbee69 100644 --- a/examples/rp/src/bin/pio_async.rs +++ b/examples/rp/src/bin/pio_async.rs @@ -4,9 +4,10 @@ #![no_main] use defmt::info; use embassy_executor::Spawner; -use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::{bind_interrupts, Peri}; use fixed::traits::ToFixed; use fixed_macro::types::U56F8; use {defmt_rtt as _, panic_probe as _}; @@ -15,11 +16,11 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) { +fn setup_pio_task_sm0<'d>(pio: &mut Common<'d, PIO0>, sm: &mut StateMachine<'d, PIO0, 0>, pin: Peri<'d, impl PioPin>) { // Setup sm0 // Send data serially to pin - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( ".origin 16", "set pindirs, 1", ".wrap_target", @@ -49,11 +50,11 @@ async fn pio_task_sm0(mut sm: StateMachine<'static, PIO0, 0>) { } } -fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 1>) { +fn setup_pio_task_sm1<'d>(pio: &mut Common<'d, PIO0>, sm: &mut StateMachine<'d, PIO0, 1>) { // Setupm sm1 // Read 0b10101 repeatedly until ISR is full - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( // ".origin 8", "set x, 0x15", @@ -79,11 +80,11 @@ async fn pio_task_sm1(mut sm: StateMachine<'static, PIO0, 1>) { } } -fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 2>) { +fn setup_pio_task_sm2<'d>(pio: &mut Common<'d, PIO0>, sm: &mut StateMachine<'d, PIO0, 2>) { // Setup sm2 // Repeatedly trigger IRQ 3 - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( ".origin 0", ".wrap_target", "set x,10", diff --git a/examples/rp/src/bin/pio_dma.rs b/examples/rp/src/bin/pio_dma.rs index 02700269c..64d603ba4 100644 --- a/examples/rp/src/bin/pio_dma.rs +++ b/examples/rp/src/bin/pio_dma.rs @@ -5,9 +5,10 @@ use defmt::info; use embassy_executor::Spawner; use embassy_futures::join::join; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; -use embassy_rp::{bind_interrupts, Peripheral}; use fixed::traits::ToFixed; use fixed_macro::types::U56F8; use {defmt_rtt as _, panic_probe as _}; @@ -32,7 +33,7 @@ async fn main(_spawner: Spawner) { .. } = Pio::new(pio, Irqs); - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( ".origin 0", "set pindirs,1", ".wrap_target", @@ -61,8 +62,8 @@ async fn main(_spawner: Spawner) { sm.set_config(&cfg); sm.set_enable(true); - let mut dma_out_ref = p.DMA_CH0.into_ref(); - let mut dma_in_ref = p.DMA_CH1.into_ref(); + let mut dma_out_ref = p.DMA_CH0; + let mut dma_in_ref = p.DMA_CH1; let mut dout = [0x12345678u32; 29]; for i in 1..dout.len() { dout[i] = (dout[i - 1] & 0x0fff_ffff) * 13 + 7; @@ -71,8 +72,8 @@ async fn main(_spawner: Spawner) { loop { let (rx, tx) = sm.rx_tx(); join( - tx.dma_push(dma_out_ref.reborrow(), &dout), - rx.dma_pull(dma_in_ref.reborrow(), &mut din), + tx.dma_push(dma_out_ref.reborrow(), &dout, false), + rx.dma_pull(dma_in_ref.reborrow(), &mut din, false), ) .await; for i in 0..din.len() { diff --git a/examples/rp/src/bin/pio_hd44780.rs b/examples/rp/src/bin/pio_hd44780.rs index 6c02630e0..164e6f8d3 100644 --- a/examples/rp/src/bin/pio_hd44780.rs +++ b/examples/rp/src/bin/pio_hd44780.rs @@ -7,13 +7,11 @@ use core::fmt::Write; use embassy_executor::Spawner; -use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; use embassy_rp::pwm::{self, Pwm}; -use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; use embassy_time::{Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -43,8 +41,27 @@ async fn main(_spawner: Spawner) { c }); - let mut hd = HD44780::new( - p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, ) .await; @@ -68,173 +85,3 @@ async fn main(_spawner: Spawner) { Timer::after_secs(1).await; } } - -pub struct HD44780<'l> { - dma: PeripheralRef<'l, AnyChannel>, - sm: StateMachine<'l, PIO0, 0>, - - buf: [u8; 40], -} - -impl<'l> HD44780<'l> { - pub async fn new( - pio: impl Peripheral

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

+ 'l, - rs: impl PioPin, - rw: impl PioPin, - e: impl PioPin, - db4: impl PioPin, - db5: impl PioPin, - db6: impl PioPin, - db7: impl PioPin, - ) -> HD44780<'l> { - into_ref!(dma); - - let Pio { - mut common, - mut irq0, - mut sm0, - .. - } = Pio::new(pio, irq); - - // takes command words ( <0:4>) - let prg = pio_proc::pio_asm!( - r#" - .side_set 1 opt - .origin 20 - - loop: - out x, 24 - delay: - jmp x--, delay - out pins, 4 side 1 - out null, 4 side 0 - jmp !osre, loop - irq 0 - "#, - ); - - let rs = common.make_pio_pin(rs); - let rw = common.make_pio_pin(rw); - let e = common.make_pio_pin(e); - let db4 = common.make_pio_pin(db4); - let db5 = common.make_pio_pin(db5); - let db6 = common.make_pio_pin(db6); - let db7 = common.make_pio_pin(db7); - - sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 125u8.into(); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out = ShiftConfig { - auto_fill: true, - direction: ShiftDirection::Left, - threshold: 32, - }; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - // init to 8 bit thrice - sm0.tx().push((50000 << 8) | 0x30); - sm0.tx().push((5000 << 8) | 0x30); - sm0.tx().push((200 << 8) | 0x30); - // init 4 bit - sm0.tx().push((200 << 8) | 0x20); - // set font and lines - sm0.tx().push((50 << 8) | 0x20); - sm0.tx().push(0b1100_0000); - - irq0.wait().await; - sm0.set_enable(false); - - // takes command sequences ( , data...) - // many side sets are only there to free up a delay bit! - let prg = pio_proc::pio_asm!( - r#" - .origin 27 - .side_set 1 - - .wrap_target - pull side 0 - out x 1 side 0 ; !rs - out y 7 side 0 ; #data - 1 - - ; rs/rw to e: >= 60ns - ; e high time: >= 500ns - ; e low time: >= 500ns - ; read data valid after e falling: ~5ns - ; write data hold after e falling: ~10ns - - loop: - pull side 0 - jmp !x data side 0 - command: - set pins 0b00 side 0 - jmp shift side 0 - data: - set pins 0b01 side 0 - shift: - out pins 4 side 1 [9] - nop side 0 [9] - out pins 4 side 1 [9] - mov osr null side 0 [7] - out pindirs 4 side 0 - set pins 0b10 side 0 - busy: - nop side 1 [9] - jmp pin more side 0 [9] - mov osr ~osr side 1 [9] - nop side 0 [4] - out pindirs 4 side 0 - jmp y-- loop side 0 - .wrap - more: - nop side 1 [9] - jmp busy side 0 [9] - "# - ); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 8u8.into(); // ~64ns/insn - cfg.set_jmp_pin(&db7); - cfg.set_set_pins(&[&rs, &rw]); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out.direction = ShiftDirection::Left; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - - // display on and cursor on and blinking, reset display - sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; - - Self { - dma: dma.map_into(), - sm: sm0, - buf: [0x20; 40], - } - } - - pub async fn add_line(&mut self, s: &[u8]) { - // move cursor to 0:0, prepare 16 characters - self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); - // move line 2 up - self.buf.copy_within(22..38, 3); - // move cursor to 1:0, prepare 16 characters - self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); - // file line 2 with spaces - self.buf[22..38].fill(0x20); - // copy input line - let len = s.len().min(16); - self.buf[22..22 + len].copy_from_slice(&s[0..len]); - // set cursor to 1:15 - self.buf[38..].copy_from_slice(&[0x80, 0xcf]); - - self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; - } -} diff --git a/examples/rp/src/bin/pio_i2s.rs b/examples/rp/src/bin/pio_i2s.rs index cf60e5b30..695a74cc3 100644 --- a/examples/rp/src/bin/pio_i2s.rs +++ b/examples/rp/src/bin/pio_i2s.rs @@ -13,10 +13,11 @@ use core::mem; use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::bootsel::is_bootsel_pressed; 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 embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -25,61 +26,30 @@ bind_interrupts!(struct Irqs { }); const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; #[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 Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); 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], + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + &program, ); // create two audio buffers (back and front) which will take turns being @@ -90,20 +60,20 @@ async fn main(_spawner: Spawner) { 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); + let dma_future = i2s.write(front_buffer); // fade in audio when bootsel is pressed - let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; + let fade_target = if is_bootsel_pressed(p.BOOTSEL.reborrow()) { + i32::MAX + } else { + 0 + }; // fill back buffer with fresh audio samples before awaiting the dma future for s in back_buffer.iter_mut() { diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs new file mode 100644 index 000000000..379e2b8f9 --- /dev/null +++ b/examples/rp/src/bin/pio_onewire.rs @@ -0,0 +1,87 @@ +//! This example shows how you can use PIO to read one or more `DS18B20` one-wire temperature sensors. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram, PioOneWireSearch}; +use embassy_time::Timer; +use heapless::Vec; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let mut onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + info!("Starting onewire search"); + + let mut devices = Vec::::new(); + let mut search = PioOneWireSearch::new(); + for _ in 0..10 { + if !search.is_finished() { + if let Some(address) = search.next(&mut onewire).await { + if crc8(&address.to_le_bytes()) == 0 { + info!("Found addres: {:x}", address); + let _ = devices.push(address); + } else { + warn!("Found invalid address: {:x}", address); + } + } + } + } + + info!("Search done, found {} devices", devices.len()); + + loop { + onewire.reset().await; + // Skip rom and trigger conversion, we can trigger all devices on the bus immediately + onewire.write_bytes(&[0xCC, 0x44]).await; + + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + + // Read all devices one by one + for device in &devices { + onewire.reset().await; + onewire.write_bytes(&[0x55]).await; // Match rom + onewire.write_bytes(&device.to_le_bytes()).await; + onewire.write_bytes(&[0xBE]).await; // Read scratchpad + + let mut data = [0; 9]; + onewire.read_bytes(&mut data).await; + if crc8(&data) == 0 { + let temp = ((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.; + info!("Read device {:x}: {} deg C", device, temp); + } else { + warn!("Reading device {:x} failed", device); + } + } + Timer::after_secs(1).await; + } +} + +fn crc8(data: &[u8]) -> u8 { + let mut crc = 0; + for b in data { + let mut data_byte = *b; + for _ in 0..8 { + let temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc +} diff --git a/examples/rp/src/bin/pio_pwm.rs b/examples/rp/src/bin/pio_pwm.rs index 23d63d435..7eabb2289 100644 --- a/examples/rp/src/bin/pio_pwm.rs +++ b/examples/rp/src/bin/pio_pwm.rs @@ -5,12 +5,11 @@ use core::time::Duration; use embassy_executor::Spawner; -use embassy_rp::gpio::Level; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; const REFRESH_INTERVAL: u64 = 20000; @@ -19,93 +18,14 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); // Note that PIN_25 is the led pin on the Pico - let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); pwm_pio.start(); diff --git a/examples/rp/src/bin/pio_rotary_encoder.rs b/examples/rp/src/bin/pio_rotary_encoder.rs index 58bdadbc0..2750f61ae 100644 --- a/examples/rp/src/bin/pio_rotary_encoder.rs +++ b/examples/rp/src/bin/pio_rotary_encoder.rs @@ -5,70 +5,18 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_rp::gpio::Pull; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::{bind_interrupts, pio}; -use fixed::traits::ToFixed; -use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct PioEncoder<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - pin_a: impl PioPin, - pin_b: impl PioPin, - ) -> Self { - let mut pin_a = pio.make_pio_pin(pin_a); - let mut pin_b = pio.make_pio_pin(pin_b); - pin_a.set_pull(Pull::Up); - pin_b.set_pull(Pull::Up); - sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); - - let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); - - let mut cfg = Config::default(); - cfg.set_in_pins(&[&pin_a, &pin_b]); - cfg.fifo_join = FifoJoin::RxOnly; - cfg.shift_in.direction = ShiftDirection::Left; - cfg.clock_divider = 10_000.to_fixed(); - cfg.use_program(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { sm } - } - - pub async fn read(&mut self) -> Direction { - loop { - match self.sm.rx().wait_pull().await { - 0 => return Direction::CounterClockwise, - 1 => return Direction::Clockwise, - _ => {} - } - } - } -} - -pub enum Direction { - Clockwise, - CounterClockwise, -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - - let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); - +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { let mut count = 0; loop { info!("Count: {}", count); @@ -78,3 +26,30 @@ async fn main(_spawner: Spawner) { }; } } + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/examples/rp/src/bin/pio_servo.rs b/examples/rp/src/bin/pio_servo.rs index a79540479..c52ee7492 100644 --- a/examples/rp/src/bin/pio_servo.rs +++ b/examples/rp/src/bin/pio_servo.rs @@ -5,12 +5,11 @@ use core::time::Duration; use embassy_executor::Spawner; -use embassy_rp::gpio::Level; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo @@ -22,88 +21,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - pub struct ServoBuilder<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, period: Duration, min_pulse_width: Duration, max_pulse_width: Duration, @@ -111,7 +30,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { } impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { - pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { Self { pwm, period: Duration::from_micros(REFRESH_INTERVAL), @@ -153,7 +72,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { } pub struct Servo<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, @@ -190,7 +109,8 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); let mut servo = ServoBuilder::new(pwm_pio) .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. diff --git a/examples/rp/src/bin/pio_stepper.rs b/examples/rp/src/bin/pio_stepper.rs index 4952f4fbd..3862c248b 100644 --- a/examples/rp/src/bin/pio_stepper.rs +++ b/examples/rp/src/bin/pio_stepper.rs @@ -3,143 +3,20 @@ #![no_std] #![no_main] -use core::mem::{self, MaybeUninit}; use defmt::info; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; use embassy_time::{with_timeout, Duration, Timer}; -use fixed::traits::ToFixed; -use fixed::types::extra::U8; -use fixed::FixedU32; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct PioStepper<'d, T: Instance, const SM: usize> { - irq: Irq<'d, T, SM>, - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { - pub fn new( - pio: &mut Common<'d, T>, - mut sm: StateMachine<'d, T, SM>, - irq: Irq<'d, T, SM>, - pin0: impl PioPin, - pin1: impl PioPin, - pin2: impl PioPin, - pin3: impl PioPin, - ) -> Self { - let prg = pio_proc::pio_asm!( - "pull block", - "mov x, osr", - "pull block", - "mov y, osr", - "jmp !x end", - "loop:", - "jmp !osre step", - "mov osr, y", - "step:", - "out pins, 4 [31]" - "jmp x-- loop", - "end:", - "irq 0 rel" - ); - let pin0 = pio.make_pio_pin(pin0); - let pin1 = pio.make_pio_pin(pin1); - let pin2 = pio.make_pio_pin(pin2); - let pin3 = pio.make_pio_pin(pin3); - sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); - let mut cfg = Config::default(); - cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); - cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); - cfg.use_program(&pio.load_program(&prg.program), &[]); - sm.set_config(&cfg); - sm.set_enable(true); - Self { irq, sm } - } - - // Set pulse frequency - pub fn set_frequency(&mut self, freq: u32) { - let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); - assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); - assert!(clock_divider >= 1, "clkdiv must be >= 1"); - self.sm.set_clock_divider(clock_divider); - self.sm.clkdiv_restart(); - } - - // Full step, one phase - pub async fn step(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await - } else { - self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await - } - } - - // Full step, two phase - pub async fn step2(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await - } else { - self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await - } - } - - // Half step - pub async fn step_half(&mut self, steps: i32) { - if steps > 0 { - self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await - } else { - self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await - } - } - - async fn run(&mut self, steps: i32, pattern: u32) { - self.sm.tx().wait_push(steps as u32).await; - self.sm.tx().wait_push(pattern).await; - let drop = OnDrop::new(|| { - self.sm.clear_fifos(); - unsafe { - self.sm.exec_instr( - pio::InstructionOperands::JMP { - address: 0, - condition: pio::JmpCondition::Always, - } - .encode(), - ); - } - }); - self.irq.wait().await; - drop.defuse(); - } -} - -struct OnDrop { - f: MaybeUninit, -} - -impl OnDrop { - pub fn new(f: F) -> Self { - Self { f: MaybeUninit::new(f) } - } - - pub fn defuse(self) { - mem::forget(self) - } -} - -impl Drop for OnDrop { - fn drop(&mut self) { - unsafe { self.f.as_ptr().read()() } - } -} - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); @@ -147,14 +24,18 @@ async fn main(_spawner: Spawner) { mut common, irq0, sm0, .. } = Pio::new(p.PIO0, Irqs); - let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); stepper.set_frequency(120); loop { info!("CW full steps"); stepper.step(1000).await; info!("CCW full steps, drop after 1 sec"); - if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { info!("Time's up!"); Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/rp/src/bin/pio_uart.rs b/examples/rp/src/bin/pio_uart.rs index 53b696309..485c65204 100644 --- a/examples/rp/src/bin/pio_uart.rs +++ b/examples/rp/src/bin/pio_uart.rs @@ -13,10 +13,10 @@ use defmt::{info, panic, trace}; use embassy_executor::Spawner; use embassy_futures::join::{join, join3}; -use embassy_rp::bind_interrupts; use embassy_rp::peripherals::{PIO0, USB}; -use embassy_rp::pio::InterruptHandler as PioInterruptHandler; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::pipe::Pipe; use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; @@ -25,13 +25,11 @@ use embassy_usb::{Builder, Config}; use embedded_io_async::{Read, Write}; use {defmt_rtt as _, panic_probe as _}; -use crate::uart::PioUart; -use crate::uart_rx::PioUartRx; -use crate::uart_tx::PioUartTx; +//use crate::uart::PioUart; bind_interrupts!(struct Irqs { USBCTRL_IRQ => InterruptHandler; - PIO0_IRQ_0 => PioInterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; }); #[embassy_executor::main] @@ -51,13 +49,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; @@ -85,8 +76,15 @@ async fn main(_spawner: Spawner) { let usb_fut = usb.run(); // PIO UART setup - let uart = PioUart::new(9600, p.PIO0, p.PIN_4, p.PIN_5); - let (mut uart_tx, mut uart_rx) = uart.split(); + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); // Pipe setup let mut usb_pipe: Pipe = Pipe::new(); @@ -163,8 +161,8 @@ async fn usb_write<'d, T: Instance + 'd>( } /// Read from the UART and write it to the USB TX pipe -async fn uart_read( - uart_rx: &mut PioUartRx<'_>, +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, ) -> ! { let mut buf = [0; 64]; @@ -180,8 +178,8 @@ async fn uart_read( } /// Read from the UART TX pipe and write it to the UART -async fn uart_write( - uart_tx: &mut PioUartTx<'_>, +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, ) -> ! { let mut buf = [0; 64]; @@ -192,197 +190,3 @@ async fn uart_write( let _ = uart_tx.write(&data).await; } } - -mod uart { - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Pio, PioPin}; - use embassy_rp::Peripheral; - - use crate::uart_rx::PioUartRx; - use crate::uart_tx::PioUartTx; - use crate::Irqs; - - pub struct PioUart<'a> { - tx: PioUartTx<'a>, - rx: PioUartRx<'a>, - } - - impl<'a> PioUart<'a> { - pub fn new( - baud: u64, - pio: impl Peripheral

+ 'a, - tx_pin: impl PioPin, - rx_pin: impl PioPin, - ) -> PioUart<'a> { - let Pio { - mut common, sm0, sm1, .. - } = Pio::new(pio, Irqs); - - let tx = PioUartTx::new(&mut common, sm0, tx_pin, baud); - let rx = PioUartRx::new(&mut common, sm1, rx_pin, baud); - - PioUart { tx, rx } - } - - pub fn split(self) -> (PioUartTx<'a>, PioUartRx<'a>) { - (self.tx, self.rx) - } - } -} - -mod uart_tx { - use core::convert::Infallible; - - use embassy_rp::gpio::Level; - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; - use embedded_io_async::{ErrorType, Write}; - use fixed::traits::ToFixed; - use fixed_macro::types::U56F8; - - pub struct PioUartTx<'a> { - sm_tx: StateMachine<'a, PIO0, 0>, - } - - impl<'a> PioUartTx<'a> { - pub fn new( - common: &mut Common<'a, PIO0>, - mut sm_tx: StateMachine<'a, PIO0, 0>, - tx_pin: impl PioPin, - baud: u64, - ) -> Self { - let prg = pio_proc::pio_asm!( - r#" - .side_set 1 opt - - ; An 8n1 UART transmit program. - ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. - - pull side 1 [7] ; Assert stop bit, or stall with line in idle state - set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks - bitloop: ; This loop will run 8 times (8n1 UART) - out pins, 1 ; Shift 1 bit from OSR to the first OUT pin - jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. - "# - ); - let tx_pin = common.make_pio_pin(tx_pin); - sm_tx.set_pins(Level::High, &[&tx_pin]); - sm_tx.set_pin_dirs(Direction::Out, &[&tx_pin]); - - let mut cfg = Config::default(); - - cfg.set_out_pins(&[&tx_pin]); - cfg.use_program(&common.load_program(&prg.program), &[&tx_pin]); - cfg.shift_out.auto_fill = false; - cfg.shift_out.direction = ShiftDirection::Right; - cfg.fifo_join = FifoJoin::TxOnly; - cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed(); - sm_tx.set_config(&cfg); - sm_tx.set_enable(true); - - Self { sm_tx } - } - - pub async fn write_u8(&mut self, data: u8) { - self.sm_tx.tx().wait_push(data as u32).await; - } - } - - impl ErrorType for PioUartTx<'_> { - type Error = Infallible; - } - - impl Write for PioUartTx<'_> { - async fn write(&mut self, buf: &[u8]) -> Result { - for byte in buf { - self.write_u8(*byte).await; - } - Ok(buf.len()) - } - } -} - -mod uart_rx { - use core::convert::Infallible; - - use embassy_rp::gpio::Level; - use embassy_rp::peripherals::PIO0; - use embassy_rp::pio::{Common, Config, Direction, FifoJoin, PioPin, ShiftDirection, StateMachine}; - use embedded_io_async::{ErrorType, Read}; - use fixed::traits::ToFixed; - use fixed_macro::types::U56F8; - - pub struct PioUartRx<'a> { - sm_rx: StateMachine<'a, PIO0, 1>, - } - - impl<'a> PioUartRx<'a> { - pub fn new( - common: &mut Common<'a, PIO0>, - mut sm_rx: StateMachine<'a, PIO0, 1>, - rx_pin: impl PioPin, - baud: u64, - ) -> Self { - let prg = pio_proc::pio_asm!( - r#" - ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and - ; break conditions more gracefully. - ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. - - start: - wait 0 pin 0 ; Stall until start bit is asserted - set x, 7 [10] ; Preload bit counter, then delay until halfway through - rx_bitloop: ; the first data bit (12 cycles incl wait, set). - in pins, 1 ; Shift data bit into ISR - jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles - jmp pin good_rx_stop ; Check stop bit (should be high) - - irq 4 rel ; Either a framing error or a break. Set a sticky flag, - wait 1 pin 0 ; and wait for line to return to idle state. - jmp start ; Don't push data if we didn't see good framing. - - good_rx_stop: ; No delay before returning to start; a little slack is - in null 24 - push ; important in case the TX clock is slightly too fast. - "# - ); - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[]); - - let rx_pin = common.make_pio_pin(rx_pin); - sm_rx.set_pins(Level::High, &[&rx_pin]); - cfg.set_in_pins(&[&rx_pin]); - cfg.set_jmp_pin(&rx_pin); - sm_rx.set_pin_dirs(Direction::In, &[&rx_pin]); - - cfg.clock_divider = (U56F8!(125_000_000) / (8 * baud)).to_fixed(); - cfg.shift_in.auto_fill = false; - cfg.shift_in.direction = ShiftDirection::Right; - cfg.shift_in.threshold = 32; - cfg.fifo_join = FifoJoin::RxOnly; - sm_rx.set_config(&cfg); - sm_rx.set_enable(true); - - Self { sm_rx } - } - - pub async fn read_u8(&mut self) -> u8 { - self.sm_rx.rx().wait_pull().await as u8 - } - } - - impl ErrorType for PioUartRx<'_> { - type Error = Infallible; - } - - impl Read for PioUartRx<'_> { - async fn read(&mut self, buf: &mut [u8]) -> Result { - let mut i = 0; - while i < buf.len() { - buf[i] = self.read_u8().await; - i += 1; - } - Ok(i) - } - } -} diff --git a/examples/rp/src/bin/pio_ws2812.rs b/examples/rp/src/bin/pio_ws2812.rs index ac145933c..d1fcfc471 100644 --- a/examples/rp/src/bin/pio_ws2812.rs +++ b/examples/rp/src/bin/pio_ws2812.rs @@ -6,15 +6,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -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::{Duration, Ticker, Timer}; -use fixed::types::U24F8; -use fixed_macro::fixed; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; use smart_leds::RGB8; use {defmt_rtt as _, panic_probe as _}; @@ -22,96 +18,6 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { - dma: PeripheralRef<'d, AnyChannel>, - sm: StateMachine<'d, P, S>, -} - -impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { - pub fn new( - pio: &mut Common<'d, P>, - mut sm: StateMachine<'d, P, S>, - dma: impl Peripheral

+ 'd, - pin: impl PioPin, - ) -> Self { - into_ref!(dma); - - // Setup sm0 - - // prepare the PIO program - let side_set = pio::SideSet::new(false, 1, false); - let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); - - const T1: u8 = 2; // start bit - const T2: u8 = 5; // data bit - const T3: u8 = 3; // stop bit - const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; - - let mut wrap_target = a.label(); - let mut wrap_source = a.label(); - let mut do_zero = a.label(); - a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); - a.bind(&mut wrap_target); - // Do stop bit - a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); - // Do start bit - a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); - // Do data bit = 1 - a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); - a.bind(&mut do_zero); - // Do data bit = 0 - a.nop_with_delay_and_side_set(T2 - 1, 0); - a.bind(&mut wrap_source); - - let prg = a.assemble_with_wrap(wrap_source, wrap_target); - let mut cfg = Config::default(); - - // Pin config - let out_pin = pio.make_pio_pin(pin); - cfg.set_out_pins(&[&out_pin]); - cfg.set_set_pins(&[&out_pin]); - - cfg.use_program(&pio.load_program(&prg), &[&out_pin]); - - // Clock config, measured in kHz to avoid overflows - // TODO CLOCK_FREQ should come from embassy_rp - let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); - let ws2812_freq = fixed!(800: U24F8); - let bit_freq = ws2812_freq * CYCLES_PER_BIT; - cfg.clock_divider = clock_freq / bit_freq; - - // FIFO config - cfg.fifo_join = FifoJoin::TxOnly; - cfg.shift_out = ShiftConfig { - auto_fill: true, - threshold: 24, - direction: ShiftDirection::Left, - }; - - sm.set_config(&cfg); - sm.set_enable(true); - - Self { - dma: dma.map_into(), - sm, - } - } - - pub async fn write(&mut self, colors: &[RGB8; N]) { - // Precompute the word bytes from the colors - let mut words = [0u32; N]; - for i in 0..N { - let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); - words[i] = word; - } - - // DMA transfer - self.sm.tx().dma_push(self.dma.reborrow(), &words).await; - - Timer::after_micros(55).await; - } -} - /// Input a value 0 to 255 to get a color value /// The colours are a transition r - g - b - back to r. fn wheel(mut wheel_pos: u8) -> RGB8 { @@ -142,7 +48,8 @@ async fn main(_spawner: Spawner) { // Common neopixel pins: // Thing plus: 8 // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 - let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); // Loop forever making RGB values and pushing them out to the WS2812. let mut ticker = Ticker::every(Duration::from_millis(10)); diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs index 26e233260..04374323d 100644 --- a/examples/rp/src/bin/pwm.rs +++ b/examples/rp/src/bin/pwm.rs @@ -1,24 +1,37 @@ //! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. //! -//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle #![no_std] #![no_main] use defmt::*; use embassy_executor::Spawner; -use embassy_rp::pwm::{Config, Pwm}; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; +use embassy_rp::Peri; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] -async fn main(_spawner: Spawner) { +async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} - let mut c: Config = Default::default(); - c.top = 0x8000; +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: Peri<'static, PWM_SLICE4>, pin25: Peri<'static, PIN_25>) { + let mut c = Config::default(); + c.top = 32_768; c.compare_b = 8; - let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); loop { info!("current LED duty cycle: {}/32768", c.compare_b); @@ -27,3 +40,41 @@ async fn main(_spawner: Spawner) { pwm.set_config(&c); } } + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: Peri<'static, PWM_SLICE2>, pin4: Peri<'static, PIN_4>) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: + let desired_freq_hz = 25_000; + let clock_freq_hz = embassy_rp::clocks::clk_sys_freq(); + let divider = 16u8; + let period = (clock_freq_hz / (desired_freq_hz * divider as u32)) as u16 - 1; + + let mut c = Config::default(); + c.top = period; + c.divider = divider.into(); + + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(c.top / 4).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp/src/bin/shared_bus.rs b/examples/rp/src/bin/shared_bus.rs index c6cb5d64c..9267dfccb 100644 --- a/examples/rp/src/bin/shared_bus.rs +++ b/examples/rp/src/bin/shared_bus.rs @@ -8,7 +8,7 @@ use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; -use embassy_rp::gpio::{AnyPin, Level, Output}; +use embassy_rp::gpio::{Level, Output}; use embassy_rp::i2c::{self, I2c, InterruptHandler}; use embassy_rp::peripherals::{I2C1, SPI1}; use embassy_rp::spi::{self, Spi}; @@ -45,8 +45,8 @@ async fn main(spawner: Spawner) { let spi_bus = SPI_BUS.init(Mutex::new(spi)); // Chip select pins for the SPI devices - let cs_a = Output::new(AnyPin::from(p.PIN_0), Level::High); - let cs_b = Output::new(AnyPin::from(p.PIN_1), Level::High); + let cs_a = Output::new(p.PIN_0, Level::High); + let cs_b = Output::new(p.PIN_1, Level::High); spawner.must_spawn(spi_task_a(spi_bus, cs_a)); spawner.must_spawn(spi_task_b(spi_bus, cs_b)); diff --git a/examples/rp/src/bin/sharing.rs b/examples/rp/src/bin/sharing.rs index 5416e20ce..856be6ace 100644 --- a/examples/rp/src/bin/sharing.rs +++ b/examples/rp/src/bin/sharing.rs @@ -27,11 +27,10 @@ use embassy_rp::{bind_interrupts, interrupt}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::{blocking_mutex, mutex}; use embassy_time::{Duration, Ticker}; -use rand::RngCore; use static_cell::{ConstStaticCell, StaticCell}; use {defmt_rtt as _, panic_probe as _}; -type UartAsyncMutex = mutex::Mutex>; +type UartAsyncMutex = mutex::Mutex>; struct MyType { inner: u32, diff --git a/examples/rp/src/bin/spi_display.rs b/examples/rp/src/bin/spi_display.rs index e937b9d0a..dd114a4ae 100644 --- a/examples/rp/src/bin/spi_display.rs +++ b/examples/rp/src/bin/spi_display.rs @@ -9,11 +9,12 @@ use core::cell::RefCell; use defmt::*; +use display_interface_spi::SPIInterface; use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; use embassy_executor::Spawner; use embassy_rp::gpio::{Level, Output}; use embassy_rp::spi; -use embassy_rp::spi::{Blocking, Spi}; +use embassy_rp::spi::Spi; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embassy_time::Delay; @@ -24,10 +25,11 @@ use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::prelude::*; use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; use embedded_graphics::text::Text; -use st7789::{Orientation, ST7789}; +use mipidsi::models::ST7789; +use mipidsi::options::{Orientation, Rotation}; +use mipidsi::Builder; use {defmt_rtt as _, panic_probe as _}; -use crate::my_display_interface::SPIDeviceInterface; use crate::touch::Touch; const DISPLAY_FREQ: u32 = 64_000_000; @@ -58,7 +60,7 @@ async fn main(_spawner: Spawner) { touch_config.phase = spi::Phase::CaptureOnSecondTransition; touch_config.polarity = spi::Polarity::IdleHigh; - let spi: Spi<'_, _, Blocking> = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); + let spi = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); @@ -74,17 +76,15 @@ async fn main(_spawner: Spawner) { let _bl = Output::new(bl, Level::High); // display interface abstraction from SPI and DC - let di = SPIDeviceInterface::new(display_spi, dcx); - - // create driver - let mut display = ST7789::new(di, rst, 240, 320); - - // initialize - display.init(&mut Delay).unwrap(); - - // set default orientation - display.set_orientation(Orientation::Landscape).unwrap(); + let di = SPIInterface::new(display_spi, dcx); + // Define the display from the display interface and initialize it + let mut display = Builder::new(ST7789, di) + .display_size(240, 320) + .reset_pin(rst) + .orientation(Orientation::new().rotate(Rotation::Deg90)) + .init(&mut Delay) + .unwrap(); display.clear(Rgb565::BLACK).unwrap(); let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), 86); @@ -175,138 +175,3 @@ mod touch { } } } - -mod my_display_interface { - use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; - use embedded_hal_1::digital::OutputPin; - use embedded_hal_1::spi::SpiDevice; - - /// SPI display interface. - /// - /// This combines the SPI peripheral and a data/command pin - pub struct SPIDeviceInterface { - spi: SPI, - dc: DC, - } - - impl SPIDeviceInterface - where - SPI: SpiDevice, - DC: OutputPin, - { - /// Create new SPI interface for communciation with a display driver - pub fn new(spi: SPI, dc: DC) -> Self { - Self { spi, dc } - } - } - - impl WriteOnlyDataCommand for SPIDeviceInterface - where - SPI: SpiDevice, - DC: OutputPin, - { - fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { - // 1 = data, 0 = command - self.dc.set_low().map_err(|_| DisplayError::DCError)?; - - send_u8(&mut self.spi, cmds).map_err(|_| DisplayError::BusWriteError)?; - Ok(()) - } - - fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { - // 1 = data, 0 = command - self.dc.set_high().map_err(|_| DisplayError::DCError)?; - - send_u8(&mut self.spi, buf).map_err(|_| DisplayError::BusWriteError)?; - Ok(()) - } - } - - fn send_u8(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> { - match words { - DataFormat::U8(slice) => spi.write(slice), - DataFormat::U16(slice) => { - use byte_slice_cast::*; - spi.write(slice.as_byte_slice()) - } - DataFormat::U16LE(slice) => { - use byte_slice_cast::*; - for v in slice.as_mut() { - *v = v.to_le(); - } - spi.write(slice.as_byte_slice()) - } - DataFormat::U16BE(slice) => { - use byte_slice_cast::*; - for v in slice.as_mut() { - *v = v.to_be(); - } - spi.write(slice.as_byte_slice()) - } - DataFormat::U8Iter(iter) => { - let mut buf = [0; 32]; - let mut i = 0; - - for v in iter.into_iter() { - buf[i] = v; - i += 1; - - if i == buf.len() { - spi.write(&buf)?; - i = 0; - } - } - - if i > 0 { - spi.write(&buf[..i])?; - } - - Ok(()) - } - DataFormat::U16LEIter(iter) => { - use byte_slice_cast::*; - let mut buf = [0; 32]; - let mut i = 0; - - for v in iter.map(u16::to_le) { - buf[i] = v; - i += 1; - - if i == buf.len() { - spi.write(&buf.as_byte_slice())?; - i = 0; - } - } - - if i > 0 { - spi.write(&buf[..i].as_byte_slice())?; - } - - Ok(()) - } - DataFormat::U16BEIter(iter) => { - use byte_slice_cast::*; - let mut buf = [0; 64]; - let mut i = 0; - let len = buf.len(); - - for v in iter.map(u16::to_be) { - buf[i] = v; - i += 1; - - if i == len { - spi.write(&buf.as_byte_slice())?; - i = 0; - } - } - - if i > 0 { - spi.write(&buf[..i].as_byte_slice())?; - } - - Ok(()) - } - _ => unimplemented!(), - } - } -} diff --git a/examples/rp/src/bin/spi_gc9a01.rs b/examples/rp/src/bin/spi_gc9a01.rs new file mode 100644 index 000000000..fdef09d4b --- /dev/null +++ b/examples/rp/src/bin/spi_gc9a01.rs @@ -0,0 +1,125 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example written for a display using the GC9A01 chip. Possibly the Waveshare RP2040-LCD-1.28 +//! (https://www.waveshare.com/wiki/RP2040-LCD-1.28) + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::*; +use display_interface_spi::SPIInterface; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::spi; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Delay, Duration, Timer}; +use embedded_graphics::image::{Image, ImageRawLE}; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use mipidsi::models::GC9A01; +use mipidsi::options::{ColorInversion, ColorOrder}; +use mipidsi::Builder; +use {defmt_rtt as _, panic_probe as _}; + +const DISPLAY_FREQ: u32 = 64_000_000; +const LCD_X_RES: i32 = 240; +const LCD_Y_RES: i32 = 240; +const FERRIS_WIDTH: u32 = 86; +const FERRIS_HEIGHT: u32 = 64; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + info!("Hello World!"); + + let bl = p.PIN_25; + let rst = p.PIN_12; + let display_cs = p.PIN_9; + let dcx = p.PIN_8; + let mosi = p.PIN_11; + let clk = p.PIN_10; + + // create SPI + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + + let spi: Spi<'_, _, Blocking> = Spi::new_blocking_txonly(p.SPI1, clk, mosi, display_config.clone()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let dcx = Output::new(dcx, Level::Low); + let rst = Output::new(rst, Level::Low); + // dcx: 0 = command, 1 = data + + // Enable LCD backlight + let _bl = Output::new(bl, Level::High); + + // display interface abstraction from SPI and DC + let di = SPIInterface::new(display_spi, dcx); + + // Define the display from the display interface and initialize it + let mut display = Builder::new(GC9A01, di) + .display_size(240, 240) + .reset_pin(rst) + .color_order(ColorOrder::Bgr) + .invert_colors(ColorInversion::Inverted) + .init(&mut Delay) + .unwrap(); + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), FERRIS_WIDTH); + let mut ferris = Image::new(&raw_image_data, Point::zero()); + + let r = rng.next_u32(); + let mut delta = Point { + x: ((r % 10) + 5) as i32, + y: (((r >> 8) % 10) + 5) as i32, + }; + loop { + // Move Ferris + let bb = ferris.bounding_box(); + let tl = bb.top_left; + let br = bb.bottom_right().unwrap(); + if tl.x < 0 || br.x > LCD_X_RES { + delta.x = -delta.x; + } + if tl.y < 0 || br.y > LCD_Y_RES { + delta.y = -delta.y; + } + + // Erase ghosting + let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLACK).build(); + let mut off = Point { x: 0, y: 0 }; + if delta.x < 0 { + off.x = FERRIS_WIDTH as i32; + } + Rectangle::new(tl + off, Size::new(delta.x as u32, FERRIS_HEIGHT)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + off = Point { x: 0, y: 0 }; + if delta.y < 0 { + off.y = FERRIS_HEIGHT as i32; + } + Rectangle::new(tl + off, Size::new(FERRIS_WIDTH, delta.y as u32)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + // Translate Ferris + ferris.translate_mut(delta); + // Display the image + ferris.draw(&mut display).unwrap(); + Timer::after(Duration::from_millis(50)).await; + } +} diff --git a/examples/rp/src/bin/spi_sdmmc.rs b/examples/rp/src/bin/spi_sdmmc.rs index 4cbc82f7b..4e3c2f199 100644 --- a/examples/rp/src/bin/spi_sdmmc.rs +++ b/examples/rp/src/bin/spi_sdmmc.rs @@ -7,7 +7,6 @@ #![no_main] use defmt::*; -use embassy_embedded_hal::SetConfig; use embassy_executor::Spawner; use embassy_rp::spi::Spi; use embassy_rp::{gpio, spi}; @@ -33,7 +32,6 @@ impl embedded_sdmmc::TimeSource for DummyTimesource { #[embassy_executor::main] async fn main(_spawner: Spawner) { - embassy_rp::pac::SIO.spinlock(31).write_value(1); let p = embassy_rp::init(Default::default()); // SPI clock needs to be running at <= 400kHz during initialization @@ -51,7 +49,7 @@ async fn main(_spawner: Spawner) { // Now that the card is initialized, the SPI clock can go faster let mut config = spi::Config::default(); config.frequency = 16_000_000; - sdcard.spi(|dev| dev.bus_mut().set_config(&config)).ok(); + sdcard.spi(|dev| dev.bus_mut().set_config(&config)); // Now let's look for volumes (also known as partitions) on our block device. // To do this we need a Volume Manager. It will take ownership of the block device. diff --git a/examples/rp/src/bin/uart_buffered_split.rs b/examples/rp/src/bin/uart_buffered_split.rs index 468d2b61a..3adbc18ab 100644 --- a/examples/rp/src/bin/uart_buffered_split.rs +++ b/examples/rp/src/bin/uart_buffered_split.rs @@ -30,7 +30,7 @@ async fn main(spawner: Spawner) { let tx_buf = &mut TX_BUF.init([0; 16])[..]; static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); let rx_buf = &mut RX_BUF.init([0; 16])[..]; - let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let uart = BufferedUart::new(uart, tx_pin, rx_pin, Irqs, tx_buf, rx_buf, Config::default()); let (mut tx, rx) = uart.split(); unwrap!(spawner.spawn(reader(rx))); @@ -48,7 +48,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: BufferedUartRx<'static, UART0>) { +async fn reader(mut rx: BufferedUartRx) { info!("Reading..."); loop { let mut buf = [0; 31]; diff --git a/examples/rp/src/bin/uart_unidir.rs b/examples/rp/src/bin/uart_unidir.rs index a45f40756..c2c8dfad8 100644 --- a/examples/rp/src/bin/uart_unidir.rs +++ b/examples/rp/src/bin/uart_unidir.rs @@ -39,7 +39,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: UartRx<'static, UART1, Async>) { +async fn reader(mut rx: UartRx<'static, Async>) { info!("Reading..."); loop { // read a total of 4 transmissions (32 / 8) and then print the result diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 03c510f37..171f21a75 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -8,7 +8,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_rp::clocks::RoscRng; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, InterruptHandler}; @@ -17,7 +17,6 @@ use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, Config, UsbDevice}; use embedded_io_async::Write; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -40,8 +39,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -60,12 +59,6 @@ async fn main(spawner: Spawner) { config.max_power = 100; config.max_packet_size_0 = 64; - // Required for Windows support. - config.composite_with_iads = true; - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - // Create embassy-usb DeviceBuilder using the driver and config. static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); @@ -108,11 +101,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/rp/src/bin/usb_hid_mouse.rs b/examples/rp/src/bin/usb_hid_mouse.rs old mode 100644 new mode 100755 index cce344fb0..4454c593c --- a/examples/rp/src/bin/usb_hid_mouse.rs +++ b/examples/rp/src/bin/usb_hid_mouse.rs @@ -8,7 +8,6 @@ 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; @@ -75,12 +74,6 @@ async fn main(_spawner: Spawner) { // 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! @@ -92,8 +85,8 @@ async fn main(_spawner: Spawner) { _ = 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 + x: rng.random_range(-100..100), // random small x movement + y: rng.random_range(-100..100), // random small y movement wheel: 0, pan: 0, }; diff --git a/examples/rp/src/bin/usb_midi.rs b/examples/rp/src/bin/usb_midi.rs index 11db1b2e1..3b7910f8b 100644 --- a/examples/rp/src/bin/usb_midi.rs +++ b/examples/rp/src/bin/usb_midi.rs @@ -37,13 +37,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/rp/src/bin/usb_raw.rs b/examples/rp/src/bin/usb_raw.rs index 97e7e0244..5974c04c0 100644 --- a/examples/rp/src/bin/usb_raw.rs +++ b/examples/rp/src/bin/usb_raw.rs @@ -84,13 +84,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/rp/src/bin/usb_raw_bulk.rs b/examples/rp/src/bin/usb_raw_bulk.rs index 331c3da4c..103269791 100644 --- a/examples/rp/src/bin/usb_raw_bulk.rs +++ b/examples/rp/src/bin/usb_raw_bulk.rs @@ -62,13 +62,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs index 4a802994a..5e3f0f378 100644 --- a/examples/rp/src/bin/usb_serial.rs +++ b/examples/rp/src/bin/usb_serial.rs @@ -37,13 +37,6 @@ async fn main(spawner: Spawner) { 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; config }; diff --git a/examples/rp/src/bin/usb_serial_with_handler.rs b/examples/rp/src/bin/usb_serial_with_handler.rs new file mode 100644 index 000000000..a9e65be70 --- /dev/null +++ b/examples/rp/src/bin/usb_serial_with_handler.rs @@ -0,0 +1,64 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates the possibility to send log::info/warn/error/debug! to USB serial port. + +#![no_std] +#![no_main] + +use core::str; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::rom_data::reset_to_usb_boot; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_time::Timer; +use embassy_usb_logger::ReceiverHandler; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +struct Handler; + +impl ReceiverHandler for Handler { + async fn handle_data(&self, data: &[u8]) { + if let Ok(data) = str::from_utf8(data) { + let data = data.trim(); + + // If you are using elf2uf2-term with the '-t' flag, then when closing the serial monitor, + // this will automatically put the pico into boot mode + if data == "q" || data == "elf2uf2-term" { + reset_to_usb_boot(0, 0); // Restart the chip + } else if data.eq_ignore_ascii_case("hello") { + log::info!("World!"); + } else { + log::info!("Recieved: {:?}", data); + } + } + } + + fn new() -> Self { + Self + } +} + +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver, Handler); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let driver = Driver::new(p.USB, Irqs); + spawner.spawn(logger_task(driver)).unwrap(); + + let mut counter = 0; + loop { + counter += 1; + log::info!("Tick {}", counter); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp/src/bin/usb_serial_with_logger.rs b/examples/rp/src/bin/usb_serial_with_logger.rs index f9cfdef94..ea13a1e27 100644 --- a/examples/rp/src/bin/usb_serial_with_logger.rs +++ b/examples/rp/src/bin/usb_serial_with_logger.rs @@ -37,13 +37,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/rp/src/bin/usb_webusb.rs b/examples/rp/src/bin/usb_webusb.rs index e73938ac9..a5dc94d5b 100644 --- a/examples/rp/src/bin/usb_webusb.rs +++ b/examples/rp/src/bin/usb_webusb.rs @@ -51,12 +51,6 @@ async fn main(_spawner: Spawner) { 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 = 0xff; - config.device_sub_class = 0x00; - config.device_protocol = 0x00; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/rp/src/bin/wifi_ap_tcp_server.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs index 00f404a9b..856838a8c 100644 --- a/examples/rp/src/bin/wifi_ap_tcp_server.rs +++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs @@ -7,11 +7,11 @@ use core::str::from_utf8; -use cyw43_pio::PioSpi; +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -19,7 +19,6 @@ use embassy_rp::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_time::Duration; use embedded_io_async::Write; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -33,8 +32,8 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -57,7 +56,16 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); @@ -80,16 +88,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::new()), - seed, - )); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); //control.start_ap_open("cyw43", 5).await; control.start_ap_wpa2("cyw43", "password", 5).await; diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs index 04a61bbd5..6e91ce167 100644 --- a/examples/rp/src/bin/wifi_blinky.rs +++ b/examples/rp/src/bin/wifi_blinky.rs @@ -5,7 +5,7 @@ #![no_std] #![no_main] -use cyw43_pio::PioSpi; +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; use defmt::*; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; @@ -33,15 +33,24 @@ async fn main(spawner: Spawner) { // To make flashing faster for development, you may want to flash the firmwares independently // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: - // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 - // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 + // probe-rs download ../../cyw43-firmware/43439A0.bin --binary-format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download ../../cyw43-firmware/43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x10140000 //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); diff --git a/examples/rp/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs index ab3529112..fe9c363d9 100644 --- a/examples/rp/src/bin/wifi_scan.rs +++ b/examples/rp/src/bin/wifi_scan.rs @@ -7,10 +7,9 @@ use core::str; -use cyw43_pio::PioSpi; +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; use defmt::*; use embassy_executor::Spawner; -use embassy_net::Stack; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIO0}; @@ -27,11 +26,6 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat runner.run().await } -#[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await -} - #[embassy_executor::main] async fn main(spawner: Spawner) { info!("Hello World!"); @@ -51,7 +45,16 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); diff --git a/examples/rp/src/bin/wifi_tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs index 61eeb82f7..fbc957e0e 100644 --- a/examples/rp/src/bin/wifi_tcp_server.rs +++ b/examples/rp/src/bin/wifi_tcp_server.rs @@ -7,11 +7,12 @@ use core::str::from_utf8; -use cyw43_pio::PioSpi; +use cyw43::JoinOptions; +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -19,7 +20,6 @@ use embassy_rp::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_time::{Duration, Timer}; use embedded_io_async::Write; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -27,8 +27,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -const WIFI_NETWORK: &str = "LadronDeWifi"; -const WIFI_PASSWORD: &str = "MBfcaedHmyRFE4kaQ1O5SsY8"; +const WIFI_NETWORK: &str = "ssid"; // change to your network SSID +const WIFI_PASSWORD: &str = "pwd"; // change to your network password #[embassy_executor::task] async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { @@ -36,8 +36,8 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -60,7 +60,16 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); @@ -83,20 +92,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::new()), - seed, - )); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { - //control.join_open(WIFI_NETWORK).await; - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); diff --git a/examples/rp/src/bin/wifi_webrequest.rs b/examples/rp/src/bin/wifi_webrequest.rs index 889371241..1efd1cd28 100644 --- a/examples/rp/src/bin/wifi_webrequest.rs +++ b/examples/rp/src/bin/wifi_webrequest.rs @@ -7,19 +7,19 @@ use core::str::from_utf8; -use cyw43_pio::PioSpi; +use cyw43::JoinOptions; +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; use defmt::*; use embassy_executor::Spawner; use embassy_net::dns::DnsSocket; use embassy_net::tcp::client::{TcpClient, TcpClientState}; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_time::{Duration, Timer}; -use rand::RngCore; use reqwless::client::{HttpClient, TlsConfig, TlsVerify}; use reqwless::request::Method; use serde::Deserialize; @@ -39,8 +39,8 @@ async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stat } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -62,7 +62,16 @@ async fn main(spawner: Spawner) { let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); @@ -86,20 +95,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::new()), - seed, - )); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { - //match control.join_open(WIFI_NETWORK).await { // for open networks - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); diff --git a/examples/rp/src/bin/zerocopy.rs b/examples/rp/src/bin/zerocopy.rs index 39f03c8e4..d1fb0eb00 100644 --- a/examples/rp/src/bin/zerocopy.rs +++ b/examples/rp/src/bin/zerocopy.rs @@ -9,9 +9,9 @@ use core::sync::atomic::{AtomicU16, Ordering}; use defmt::*; use embassy_executor::Spawner; use embassy_rp::adc::{self, Adc, Async, Config, InterruptHandler}; -use embassy_rp::bind_interrupts; use embassy_rp::gpio::Pull; use embassy_rp::peripherals::DMA_CH0; +use embassy_rp::{bind_interrupts, Peri}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::zerocopy_channel::{Channel, Receiver, Sender}; use embassy_time::{Duration, Ticker, Timer}; @@ -31,7 +31,7 @@ static MAX: AtomicU16 = AtomicU16::new(0); struct AdcParts { adc: Adc<'static, Async>, pin: adc::Channel<'static>, - dma: DMA_CH0, + dma: Peri<'static, DMA_CH0>, } #[embassy_executor::main] @@ -70,7 +70,10 @@ async fn producer(mut sender: Sender<'static, NoopRawMutex, SampleBuffer>, mut a let buf = sender.send().await; // Fill it with data - adc.adc.read_many(&mut adc.pin, buf, 1, &mut adc.dma).await.unwrap(); + adc.adc + .read_many(&mut adc.pin, buf, 1, adc.dma.reborrow()) + .await + .unwrap(); // Notify the channel that the buffer is now ready to be received sender.send_done(); diff --git a/examples/rp23/Cargo.toml b/examples/rp23/Cargo.toml deleted file mode 100644 index 8f8d6ff10..000000000 --- a/examples/rp23/Cargo.toml +++ /dev/null @@ -1,80 +0,0 @@ -[package] -edition = "2021" -name = "embassy-rp2350-examples" -version = "0.1.0" -license = "MIT OR Apache-2.0" - - -[dependencies] -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } -embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } -embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } -cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } -cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } - -defmt = "0.3" -defmt-rtt = "0.4" -fixed = "1.23.1" -fixed-macro = "1.2" - -# for web request example -reqwless = { version = "0.12.0", features = ["defmt",]} -serde = { version = "1.0.203", default-features = false, features = ["derive"] } -serde-json-core = "0.5.1" - -# for assign resources example -assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } - -#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } -cortex-m = { version = "0.7.6", features = ["inline-asm"] } -cortex-m-rt = "0.7.0" -critical-section = "1.1" -panic-probe = { version = "0.3", features = ["print-defmt"] } -display-interface-spi = "0.4.1" -embedded-graphics = "0.7.1" -st7789 = "0.6.1" -display-interface = "0.4.1" -byte-slice-cast = { version = "1.2.0", default-features = false } -smart-leds = "0.3.0" -heapless = "0.8" -usbd-hid = "0.8.1" - -embedded-hal-1 = { package = "embedded-hal", version = "1.0" } -embedded-hal-async = "1.0" -embedded-hal-bus = { version = "0.1", features = ["async"] } -embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } -embedded-storage = { version = "0.3" } -static_cell = "2.1" -portable-atomic = { version = "1.5", features = ["critical-section"] } -log = "0.4" -pio-proc = "0.2" -pio = "0.2.1" -rand = { version = "0.8.5", default-features = false } -embedded-sdmmc = "0.7.0" - -bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } -trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } - -[profile.release] -debug = 2 - -[profile.dev] -lto = true -opt-level = "z" - -[patch.crates-io] -trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } -bt-hci = { git = "https://github.com/alexmoon/bt-hci.git", rev = "b9cd5954f6bd89b535cad9c418e9fdf12812d7c3" } -embassy-executor = { path = "../../embassy-executor" } -embassy-sync = { path = "../../embassy-sync" } -embassy-futures = { path = "../../embassy-futures" } -embassy-time = { path = "../../embassy-time" } -embassy-time-driver = { path = "../../embassy-time-driver" } -embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/examples/rp23/src/bin/pio_hd44780.rs b/examples/rp23/src/bin/pio_hd44780.rs deleted file mode 100644 index fc658267d..000000000 --- a/examples/rp23/src/bin/pio_hd44780.rs +++ /dev/null @@ -1,255 +0,0 @@ -//! This example shows powerful PIO module in the RP2040 chip to communicate with a HD44780 display. -//! See (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) - -#![no_std] -#![no_main] - -use core::fmt::Write; - -use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; -use embassy_rp::dma::{AnyChannel, Channel}; -use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; -use embassy_rp::pwm::{self, Pwm}; -use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; -use embassy_time::{Instant, Timer}; -use {defmt_rtt as _, panic_probe as _}; - -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -bind_interrupts!(pub struct Irqs { - PIO0_IRQ_0 => InterruptHandler; -}); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // this test assumes a 2x16 HD44780 display attached as follow: - // rs = PIN0 - // rw = PIN1 - // e = PIN2 - // db4 = PIN3 - // db5 = PIN4 - // db6 = PIN5 - // db7 = PIN6 - // additionally a pwm signal for a bias voltage charge pump is provided on pin 15, - // allowing direct connection of the display to the RP2040 without level shifters. - let p = embassy_rp::init(Default::default()); - - let _pwm = Pwm::new_output_b(p.PWM_SLICE7, p.PIN_15, { - let mut c = pwm::Config::default(); - c.divider = 125.into(); - c.top = 100; - c.compare_b = 50; - c - }); - - let mut hd = HD44780::new( - p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, - ) - .await; - - loop { - struct Buf([u8; N], usize); - impl Write for Buf { - fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { - for b in s.as_bytes() { - if self.1 >= N { - return Err(core::fmt::Error); - } - self.0[self.1] = *b; - self.1 += 1; - } - Ok(()) - } - } - let mut buf = Buf([0; 16], 0); - write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap(); - hd.add_line(&buf.0[0..buf.1]).await; - Timer::after_secs(1).await; - } -} - -pub struct HD44780<'l> { - dma: PeripheralRef<'l, AnyChannel>, - sm: StateMachine<'l, PIO0, 0>, - - buf: [u8; 40], -} - -impl<'l> HD44780<'l> { - pub async fn new( - pio: impl Peripheral

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

+ 'l, - rs: impl PioPin, - rw: impl PioPin, - e: impl PioPin, - db4: impl PioPin, - db5: impl PioPin, - db6: impl PioPin, - db7: impl PioPin, - ) -> HD44780<'l> { - into_ref!(dma); - - let Pio { - mut common, - mut irq0, - mut sm0, - .. - } = Pio::new(pio, irq); - - // takes command words ( <0:4>) - let prg = pio_proc::pio_asm!( - r#" - .side_set 1 opt - .origin 20 - - loop: - out x, 24 - delay: - jmp x--, delay - out pins, 4 side 1 - out null, 4 side 0 - jmp !osre, loop - irq 0 - "#, - ); - - let rs = common.make_pio_pin(rs); - let rw = common.make_pio_pin(rw); - let e = common.make_pio_pin(e); - let db4 = common.make_pio_pin(db4); - let db5 = common.make_pio_pin(db5); - let db6 = common.make_pio_pin(db6); - let db7 = common.make_pio_pin(db7); - - sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 125u8.into(); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out = ShiftConfig { - auto_fill: true, - direction: ShiftDirection::Left, - threshold: 32, - }; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - // init to 8 bit thrice - sm0.tx().push((50000 << 8) | 0x30); - sm0.tx().push((5000 << 8) | 0x30); - sm0.tx().push((200 << 8) | 0x30); - // init 4 bit - sm0.tx().push((200 << 8) | 0x20); - // set font and lines - sm0.tx().push((50 << 8) | 0x20); - sm0.tx().push(0b1100_0000); - - irq0.wait().await; - sm0.set_enable(false); - - // takes command sequences ( , data...) - // many side sets are only there to free up a delay bit! - let prg = pio_proc::pio_asm!( - r#" - .origin 27 - .side_set 1 - - .wrap_target - pull side 0 - out x 1 side 0 ; !rs - out y 7 side 0 ; #data - 1 - - ; rs/rw to e: >= 60ns - ; e high time: >= 500ns - ; e low time: >= 500ns - ; read data valid after e falling: ~5ns - ; write data hold after e falling: ~10ns - - loop: - pull side 0 - jmp !x data side 0 - command: - set pins 0b00 side 0 - jmp shift side 0 - data: - set pins 0b01 side 0 - shift: - out pins 4 side 1 [9] - nop side 0 [9] - out pins 4 side 1 [9] - mov osr null side 0 [7] - out pindirs 4 side 0 - set pins 0b10 side 0 - busy: - nop side 1 [9] - jmp pin more side 0 [9] - mov osr ~osr side 1 [9] - nop side 0 [4] - out pindirs 4 side 0 - jmp y-- loop side 0 - .wrap - more: - nop side 1 [9] - jmp busy side 0 [9] - "# - ); - - let mut cfg = Config::default(); - cfg.use_program(&common.load_program(&prg.program), &[&e]); - cfg.clock_divider = 8u8.into(); // ~64ns/insn - cfg.set_jmp_pin(&db7); - cfg.set_set_pins(&[&rs, &rw]); - cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); - cfg.shift_out.direction = ShiftDirection::Left; - cfg.fifo_join = FifoJoin::TxOnly; - sm0.set_config(&cfg); - - sm0.set_enable(true); - - // display on and cursor on and blinking, reset display - sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; - - Self { - dma: dma.map_into(), - sm: sm0, - buf: [0x20; 40], - } - } - - pub async fn add_line(&mut self, s: &[u8]) { - // move cursor to 0:0, prepare 16 characters - self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); - // move line 2 up - self.buf.copy_within(22..38, 3); - // move cursor to 1:0, prepare 16 characters - self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); - // file line 2 with spaces - self.buf[22..38].fill(0x20); - // copy input line - let len = s.len().min(16); - self.buf[22..22 + len].copy_from_slice(&s[0..len]); - // set cursor to 1:15 - self.buf[38..].copy_from_slice(&[0x80, 0xcf]); - - self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; - } -} diff --git a/examples/rp23/src/bin/pio_i2s.rs b/examples/rp23/src/bin/pio_i2s.rs deleted file mode 100644 index 5a3bde759..000000000 --- a/examples/rp23/src/bin/pio_i2s.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! 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::block::ImageDef; -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 _}; - -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -bind_interrupts!(struct Irqs { - PIO0_IRQ_0 => InterruptHandler; -}); - -const SAMPLE_RATE: u32 = 48_000; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let 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 - let fade_target = i32::MAX; - - // 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/rp23/src/bin/pio_pwm.rs b/examples/rp23/src/bin/pio_pwm.rs deleted file mode 100644 index 7c5eefc45..000000000 --- a/examples/rp23/src/bin/pio_pwm.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! This example shows how to create a pwm using the PIO module in the RP2040 chip. - -#![no_std] -#![no_main] -use core::time::Duration; - -use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Level; -use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; -use embassy_time::Timer; -use pio::InstructionOperands; -use {defmt_rtt as _, panic_probe as _}; - -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -const REFRESH_INTERVAL: u64 = 20000; - -bind_interrupts!(struct Irqs { - PIO0_IRQ_0 => InterruptHandler; -}); - -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - - // Note that PIN_25 is the led pin on the Pico - let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); - pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); - pwm_pio.start(); - - let mut duration = 0; - loop { - duration = (duration + 1) % 1000; - pwm_pio.write(Duration::from_micros(duration)); - Timer::after_millis(1).await; - } -} diff --git a/examples/rp23/src/bin/pio_ws2812.rs b/examples/rp23/src/bin/pio_ws2812.rs deleted file mode 100644 index 99d9cf7e6..000000000 --- a/examples/rp23/src/bin/pio_ws2812.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! This example shows powerful PIO module in the RP2040 chip to communicate with WS2812 LED modules. -//! See (https://www.sparkfun.com/categories/tags/ws2812) - -#![no_std] -#![no_main] - -use defmt::*; -use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; -use embassy_rp::dma::{AnyChannel, Channel}; -use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{ - Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, -}; -use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; -use embassy_time::{Duration, Ticker, Timer}; -use fixed::types::U24F8; -use fixed_macro::fixed; -use smart_leds::RGB8; -use {defmt_rtt as _, panic_probe as _}; - -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -bind_interrupts!(struct Irqs { - PIO0_IRQ_0 => InterruptHandler; -}); - -pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { - dma: PeripheralRef<'d, AnyChannel>, - sm: StateMachine<'d, P, S>, -} - -impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { - pub fn new( - pio: &mut Common<'d, P>, - mut sm: StateMachine<'d, P, S>, - dma: impl Peripheral

+ 'd, - pin: impl PioPin, - ) -> Self { - into_ref!(dma); - - // Setup sm0 - - // prepare the PIO program - let side_set = pio::SideSet::new(false, 1, false); - let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); - - const T1: u8 = 2; // start bit - const T2: u8 = 5; // data bit - const T3: u8 = 3; // stop bit - const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; - - let mut wrap_target = a.label(); - let mut wrap_source = a.label(); - let mut do_zero = a.label(); - a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); - a.bind(&mut wrap_target); - // Do stop bit - a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); - // Do start bit - a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); - // Do data bit = 1 - a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); - a.bind(&mut do_zero); - // Do data bit = 0 - a.nop_with_delay_and_side_set(T2 - 1, 0); - a.bind(&mut wrap_source); - - let prg = a.assemble_with_wrap(wrap_source, wrap_target); - let mut cfg = Config::default(); - - // Pin config - let out_pin = pio.make_pio_pin(pin); - cfg.set_out_pins(&[&out_pin]); - cfg.set_set_pins(&[&out_pin]); - - cfg.use_program(&pio.load_program(&prg), &[&out_pin]); - - // Clock config, measured in kHz to avoid overflows - // TODO CLOCK_FREQ should come from embassy_rp - let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); - let ws2812_freq = fixed!(800: U24F8); - let bit_freq = ws2812_freq * CYCLES_PER_BIT; - cfg.clock_divider = clock_freq / bit_freq; - - // FIFO config - cfg.fifo_join = FifoJoin::TxOnly; - cfg.shift_out = ShiftConfig { - auto_fill: true, - threshold: 24, - direction: ShiftDirection::Left, - }; - - sm.set_config(&cfg); - sm.set_enable(true); - - Self { - dma: dma.map_into(), - sm, - } - } - - pub async fn write(&mut self, colors: &[RGB8; N]) { - // Precompute the word bytes from the colors - let mut words = [0u32; N]; - for i in 0..N { - let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); - words[i] = word; - } - - // DMA transfer - self.sm.tx().dma_push(self.dma.reborrow(), &words).await; - - Timer::after_micros(55).await; - } -} - -/// Input a value 0 to 255 to get a color value -/// The colours are a transition r - g - b - back to r. -fn wheel(mut wheel_pos: u8) -> RGB8 { - wheel_pos = 255 - wheel_pos; - if wheel_pos < 85 { - return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); - } - if wheel_pos < 170 { - wheel_pos -= 85; - return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); - } - wheel_pos -= 170; - (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() -} - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - info!("Start"); - let p = embassy_rp::init(Default::default()); - - let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - - // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit - // feather boards for the 2040 both have one built in. - const NUM_LEDS: usize = 1; - let mut data = [RGB8::default(); NUM_LEDS]; - - // Common neopixel pins: - // Thing plus: 8 - // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 - 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:"); - for i in 0..NUM_LEDS { - data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); - debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); - } - ws2812.write(&data).await; - - ticker.next().await; - } - } -} diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs deleted file mode 100644 index 4cd3b3eb4..000000000 --- a/examples/rp23/src/bin/pwm.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. -//! -//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. - -#![no_std] -#![no_main] - -use defmt::*; -use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; -use embassy_rp::pwm::{Config, Pwm}; -use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; - -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - - let mut c: Config = Default::default(); - c.top = 0x8000; - c.compare_b = 8; - let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); - - loop { - info!("current LED duty cycle: {}/32768", c.compare_b); - Timer::after_secs(1).await; - c.compare_b = c.compare_b.rotate_left(4); - pwm.set_config(&c); - } -} diff --git a/examples/rp23/src/bin/spi_display.rs b/examples/rp23/src/bin/spi_display.rs deleted file mode 100644 index 2441b1186..000000000 --- a/examples/rp23/src/bin/spi_display.rs +++ /dev/null @@ -1,327 +0,0 @@ -//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. -//! -//! Example written for a display using the ST7789 chip. Possibly the Waveshare Pico-ResTouch -//! (https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8) - -#![no_std] -#![no_main] - -use core::cell::RefCell; - -use defmt::*; -use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; -use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; -use embassy_rp::gpio::{Level, Output}; -use embassy_rp::spi; -use embassy_rp::spi::{Blocking, Spi}; -use embassy_sync::blocking_mutex::raw::NoopRawMutex; -use embassy_sync::blocking_mutex::Mutex; -use embassy_time::Delay; -use embedded_graphics::image::{Image, ImageRawLE}; -use embedded_graphics::mono_font::ascii::FONT_10X20; -use embedded_graphics::mono_font::MonoTextStyle; -use embedded_graphics::pixelcolor::Rgb565; -use embedded_graphics::prelude::*; -use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; -use embedded_graphics::text::Text; -use st7789::{Orientation, ST7789}; -use {defmt_rtt as _, panic_probe as _}; - -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -use crate::my_display_interface::SPIDeviceInterface; -use crate::touch::Touch; - -const DISPLAY_FREQ: u32 = 64_000_000; -const TOUCH_FREQ: u32 = 200_000; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - info!("Hello World!"); - - let bl = p.PIN_13; - let rst = p.PIN_15; - let display_cs = p.PIN_9; - let dcx = p.PIN_8; - let miso = p.PIN_12; - let mosi = p.PIN_11; - let clk = p.PIN_10; - let touch_cs = p.PIN_16; - //let touch_irq = p.PIN_17; - - // create SPI - let mut display_config = spi::Config::default(); - display_config.frequency = DISPLAY_FREQ; - display_config.phase = spi::Phase::CaptureOnSecondTransition; - display_config.polarity = spi::Polarity::IdleHigh; - let mut touch_config = spi::Config::default(); - touch_config.frequency = TOUCH_FREQ; - touch_config.phase = spi::Phase::CaptureOnSecondTransition; - touch_config.polarity = spi::Polarity::IdleHigh; - - let spi: Spi<'_, _, Blocking> = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); - let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); - - let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); - let touch_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(touch_cs, Level::High), touch_config); - - let mut touch = Touch::new(touch_spi); - - let dcx = Output::new(dcx, Level::Low); - let rst = Output::new(rst, Level::Low); - // dcx: 0 = command, 1 = data - - // Enable LCD backlight - let _bl = Output::new(bl, Level::High); - - // display interface abstraction from SPI and DC - let di = SPIDeviceInterface::new(display_spi, dcx); - - // create driver - let mut display = ST7789::new(di, rst, 240, 320); - - // initialize - display.init(&mut Delay).unwrap(); - - // set default orientation - display.set_orientation(Orientation::Landscape).unwrap(); - - display.clear(Rgb565::BLACK).unwrap(); - - let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), 86); - let ferris = Image::new(&raw_image_data, Point::new(34, 68)); - - // Display the image - ferris.draw(&mut display).unwrap(); - - let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN); - Text::new( - "Hello embedded_graphics \n + embassy + RP2040!", - Point::new(20, 200), - style, - ) - .draw(&mut display) - .unwrap(); - - loop { - if let Some((x, y)) = touch.read() { - let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLUE).build(); - - Rectangle::new(Point::new(x - 1, y - 1), Size::new(3, 3)) - .into_styled(style) - .draw(&mut display) - .unwrap(); - } - } -} - -/// Driver for the XPT2046 resistive touchscreen sensor -mod touch { - use embedded_hal_1::spi::{Operation, SpiDevice}; - - struct Calibration { - x1: i32, - x2: i32, - y1: i32, - y2: i32, - sx: i32, - sy: i32, - } - - const CALIBRATION: Calibration = Calibration { - x1: 3880, - x2: 340, - y1: 262, - y2: 3850, - sx: 320, - sy: 240, - }; - - pub struct Touch { - spi: SPI, - } - - impl Touch - where - SPI: SpiDevice, - { - pub fn new(spi: SPI) -> Self { - Self { spi } - } - - pub fn read(&mut self) -> Option<(i32, i32)> { - let mut x = [0; 2]; - let mut y = [0; 2]; - self.spi - .transaction(&mut [ - Operation::Write(&[0x90]), - Operation::Read(&mut x), - Operation::Write(&[0xd0]), - Operation::Read(&mut y), - ]) - .unwrap(); - - let x = (u16::from_be_bytes(x) >> 3) as i32; - let y = (u16::from_be_bytes(y) >> 3) as i32; - - let cal = &CALIBRATION; - - let x = ((x - cal.x1) * cal.sx / (cal.x2 - cal.x1)).clamp(0, cal.sx); - let y = ((y - cal.y1) * cal.sy / (cal.y2 - cal.y1)).clamp(0, cal.sy); - if x == 0 && y == 0 { - None - } else { - Some((x, y)) - } - } - } -} - -mod my_display_interface { - use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; - use embedded_hal_1::digital::OutputPin; - use embedded_hal_1::spi::SpiDevice; - - /// SPI display interface. - /// - /// This combines the SPI peripheral and a data/command pin - pub struct SPIDeviceInterface { - spi: SPI, - dc: DC, - } - - impl SPIDeviceInterface - where - SPI: SpiDevice, - DC: OutputPin, - { - /// Create new SPI interface for communciation with a display driver - pub fn new(spi: SPI, dc: DC) -> Self { - Self { spi, dc } - } - } - - impl WriteOnlyDataCommand for SPIDeviceInterface - where - SPI: SpiDevice, - DC: OutputPin, - { - fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { - // 1 = data, 0 = command - self.dc.set_low().map_err(|_| DisplayError::DCError)?; - - send_u8(&mut self.spi, cmds).map_err(|_| DisplayError::BusWriteError)?; - Ok(()) - } - - fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { - // 1 = data, 0 = command - self.dc.set_high().map_err(|_| DisplayError::DCError)?; - - send_u8(&mut self.spi, buf).map_err(|_| DisplayError::BusWriteError)?; - Ok(()) - } - } - - fn send_u8(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> { - match words { - DataFormat::U8(slice) => spi.write(slice), - DataFormat::U16(slice) => { - use byte_slice_cast::*; - spi.write(slice.as_byte_slice()) - } - DataFormat::U16LE(slice) => { - use byte_slice_cast::*; - for v in slice.as_mut() { - *v = v.to_le(); - } - spi.write(slice.as_byte_slice()) - } - DataFormat::U16BE(slice) => { - use byte_slice_cast::*; - for v in slice.as_mut() { - *v = v.to_be(); - } - spi.write(slice.as_byte_slice()) - } - DataFormat::U8Iter(iter) => { - let mut buf = [0; 32]; - let mut i = 0; - - for v in iter.into_iter() { - buf[i] = v; - i += 1; - - if i == buf.len() { - spi.write(&buf)?; - i = 0; - } - } - - if i > 0 { - spi.write(&buf[..i])?; - } - - Ok(()) - } - DataFormat::U16LEIter(iter) => { - use byte_slice_cast::*; - let mut buf = [0; 32]; - let mut i = 0; - - for v in iter.map(u16::to_le) { - buf[i] = v; - i += 1; - - if i == buf.len() { - spi.write(&buf.as_byte_slice())?; - i = 0; - } - } - - if i > 0 { - spi.write(&buf[..i].as_byte_slice())?; - } - - Ok(()) - } - DataFormat::U16BEIter(iter) => { - use byte_slice_cast::*; - let mut buf = [0; 64]; - let mut i = 0; - let len = buf.len(); - - for v in iter.map(u16::to_be) { - buf[i] = v; - i += 1; - - if i == len { - spi.write(&buf.as_byte_slice())?; - i = 0; - } - } - - if i > 0 { - spi.write(&buf[..i].as_byte_slice())?; - } - - Ok(()) - } - _ => unimplemented!(), - } - } -} diff --git a/examples/rp235x/.cargo/config.toml b/examples/rp235x/.cargo/config.toml new file mode 100644 index 000000000..40f245785 --- /dev/null +++ b/examples/rp235x/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip RP235x" +#runner = "elf2uf2-rs -d" +#runner = "picotool load -u -v -x -t elf" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/examples/rp235x/Cargo.toml b/examples/rp235x/Cargo.toml new file mode 100644 index 000000000..c81b79ae1 --- /dev/null +++ b/examples/rp235x/Cargo.toml @@ -0,0 +1,65 @@ +[package] +edition = "2021" +name = "embassy-rp2350-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.4.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } +embassy-net-wiznet = { version = "0.2.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.4.0", path = "../../embassy-usb-logger" } +cyw43 = { version = "0.3.0", path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { version = "0.4.0", path = "../../cyw43-pio", features = ["defmt"] } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" +fixed = "1.23.1" +fixed-macro = "1.2" + +serde = { version = "1.0.203", default-features = false, features = ["derive"] } +serde-json-core = "0.5.1" + +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "bd22cb7a92031fb16f74a5da42469d466c33383e" } + +# for TB6612FNG example +tb6612fng = "1.0.0" + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +display-interface-spi = "0.5.0" +embedded-graphics = "0.8.1" +mipidsi = "0.8.0" +display-interface = "0.5.0" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.3.0" +heapless = "0.8" +usbd-hid = "0.8.1" + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +embedded-sdmmc = "0.7.0" + +[profile.release] +debug = 2 + +[profile.dev] +lto = true +opt-level = "z" diff --git a/examples/rp23/assets/ferris.raw b/examples/rp235x/assets/ferris.raw similarity index 100% rename from examples/rp23/assets/ferris.raw rename to examples/rp235x/assets/ferris.raw diff --git a/examples/rp235x/build.rs b/examples/rp235x/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/rp235x/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/rp23/memory.x b/examples/rp235x/memory.x similarity index 98% rename from examples/rp23/memory.x rename to examples/rp235x/memory.x index 777492062..c803896f6 100644 --- a/examples/rp23/memory.x +++ b/examples/rp235x/memory.x @@ -31,6 +31,7 @@ SECTIONS { { __start_block_addr = .; KEEP(*(.start_block)); + KEEP(*(.boot_info)); } > FLASH } INSERT AFTER .vector_table; diff --git a/examples/rp23/src/bin/adc.rs b/examples/rp235x/src/bin/adc.rs similarity index 72% rename from examples/rp23/src/bin/adc.rs rename to examples/rp235x/src/bin/adc.rs index 19872607e..5c4135268 100644 --- a/examples/rp23/src/bin/adc.rs +++ b/examples/rp235x/src/bin/adc.rs @@ -1,4 +1,4 @@ -//! This example test the ADC (Analog to Digital Conversion) of the RS2040 pin 26, 27 and 28. +//! This example test the ADC (Analog to Digital Conversion) of the RP2350A pins 26, 27 and 28. //! It also reads the temperature sensor in the chip. #![no_std] @@ -8,25 +8,10 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::Pull; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { ADC_IRQ_FIFO => InterruptHandler; }); @@ -55,7 +40,7 @@ async fn main(_spawner: Spawner) { } fn convert_to_celsius(raw_temp: u16) -> f32 { - // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + // According to chapter 12.4.6 Temperature Sensor in RP235x datasheet let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721; let sign = if temp < 0.0 { -1.0 } else { 1.0 }; let rounded_temp_x10: i16 = ((temp * 10.0) + 0.5 * sign) as i16; diff --git a/examples/rp23/src/bin/adc_dma.rs b/examples/rp235x/src/bin/adc_dma.rs similarity index 70% rename from examples/rp23/src/bin/adc_dma.rs rename to examples/rp235x/src/bin/adc_dma.rs index d538ddaa2..4003cc078 100644 --- a/examples/rp23/src/bin/adc_dma.rs +++ b/examples/rp235x/src/bin/adc_dma.rs @@ -1,4 +1,4 @@ -//! This example shows how to use the RP2040 ADC with DMA, both single- and multichannel reads. +//! This example shows how to use the RP235x ADC with DMA, both single- and multichannel reads. //! For multichannel, the samples are interleaved in the buffer: //! `[ch1, ch2, ch3, ch4, ch1, ch2, ch3, ch4, ...]` #![no_std] @@ -8,25 +8,10 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::Pull; use embassy_time::{Duration, Ticker}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { ADC_IRQ_FIFO => InterruptHandler; }); @@ -53,13 +38,13 @@ async fn main(_spawner: Spawner) { // Read 100 samples from a single channel let mut buf = [0_u16; BLOCK_SIZE]; let div = 479; // 100kHz sample rate (48Mhz / 100kHz - 1) - adc.read_many(&mut pin, &mut buf, div, &mut dma).await.unwrap(); + adc.read_many(&mut pin, &mut buf, div, dma.reborrow()).await.unwrap(); info!("single: {:?} ...etc", buf[..8]); // Read 100 samples from 4 channels interleaved let mut buf = [0_u16; { BLOCK_SIZE * NUM_CHANNELS }]; let div = 119; // 100kHz sample rate (48Mhz / 100kHz * 4ch - 1) - adc.read_many_multichannel(&mut pins, &mut buf, div, &mut dma) + adc.read_many_multichannel(&mut pins, &mut buf, div, dma.reborrow()) .await .unwrap(); info!("multi: {:?} ...etc", buf[..NUM_CHANNELS * 2]); diff --git a/examples/rp23/src/bin/assign_resources.rs b/examples/rp235x/src/bin/assign_resources.rs similarity index 83% rename from examples/rp23/src/bin/assign_resources.rs rename to examples/rp235x/src/bin/assign_resources.rs index 923c13514..341f54d22 100644 --- a/examples/rp23/src/bin/assign_resources.rs +++ b/examples/rp235x/src/bin/assign_resources.rs @@ -14,26 +14,12 @@ use assign_resources::assign_resources; use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{self, PIN_20, PIN_21}; +use embassy_rp::Peri; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(spawner: Spawner) { // initialize the peripherals @@ -53,7 +39,11 @@ async fn main(spawner: Spawner) { // 1) Assigning a resource to a task by passing parts of the peripherals. #[embassy_executor::task] -async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) { +async fn double_blinky_manually_assigned( + _spawner: Spawner, + pin_20: Peri<'static, PIN_20>, + pin_21: Peri<'static, PIN_21>, +) { let mut led_20 = Output::new(pin_20, Level::Low); let mut led_21 = Output::new(pin_21, Level::High); diff --git a/examples/rp23/src/bin/blinky.rs b/examples/rp235x/src/bin/blinky.rs similarity index 51% rename from examples/rp23/src/bin/blinky.rs rename to examples/rp235x/src/bin/blinky.rs index 02bdf9b3d..8a2464fbb 100644 --- a/examples/rp23/src/bin/blinky.rs +++ b/examples/rp235x/src/bin/blinky.rs @@ -1,36 +1,34 @@ //! This example test the RP Pico on board LED. //! -//! It does not work with the RP Pico W board. See wifi_blinky.rs. +//! It does not work with the RP Pico W board. See `blinky_wifi.rs`. #![no_std] #![no_main] use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio; use embassy_time::Timer; use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] +// Program metadata for `picotool info`. +// This isn't needed, but it's recomended to have these minimal entries. +#[unsafe(link_section = ".bi_entries")] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), + embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), + embassy_rp::binary_info::rp_program_description!( + c"This example tests the RP Pico on board LED, connected to gpio 25" + ), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_build_attribute!(), ]; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let mut led = Output::new(p.PIN_2, Level::Low); + let mut led = Output::new(p.PIN_25, Level::Low); loop { info!("led on!"); diff --git a/examples/rp23/src/bin/blinky_two_channels.rs b/examples/rp235x/src/bin/blinky_two_channels.rs similarity index 72% rename from examples/rp23/src/bin/blinky_two_channels.rs rename to examples/rp235x/src/bin/blinky_two_channels.rs index 4d7dc89fa..51e139e94 100644 --- a/examples/rp23/src/bin/blinky_two_channels.rs +++ b/examples/rp235x/src/bin/blinky_two_channels.rs @@ -7,28 +7,13 @@ /// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::{Channel, Sender}; use embassy_time::{Duration, Ticker}; -use gpio::{AnyPin, Level, Output}; +use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - enum LedState { Toggle, } @@ -37,7 +22,7 @@ static CHANNEL: Channel = Channel::new(); #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High); + let mut led = Output::new(p.PIN_25, Level::High); let dt = 100 * 1_000_000; let k = 1.003; diff --git a/examples/rp23/src/bin/blinky_two_tasks.rs b/examples/rp235x/src/bin/blinky_two_tasks.rs similarity index 74% rename from examples/rp23/src/bin/blinky_two_tasks.rs rename to examples/rp235x/src/bin/blinky_two_tasks.rs index 24b960242..67a9108c0 100644 --- a/examples/rp23/src/bin/blinky_two_tasks.rs +++ b/examples/rp235x/src/bin/blinky_two_tasks.rs @@ -7,28 +7,13 @@ /// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Ticker}; -use gpio::{AnyPin, Level, Output}; +use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - type LedType = Mutex>>; static LED: LedType = Mutex::new(None); @@ -36,7 +21,7 @@ static LED: LedType = Mutex::new(None); async fn main(spawner: Spawner) { let p = embassy_rp::init(Default::default()); // set the content of the global LED reference to the real LED pin - let led = Output::new(AnyPin::from(p.PIN_25), Level::High); + let led = Output::new(p.PIN_25, Level::High); // inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the // Mutex is released { diff --git a/examples/rp235x/src/bin/blinky_wifi.rs b/examples/rp235x/src/bin/blinky_wifi.rs new file mode 100644 index 000000000..8c352ebc4 --- /dev/null +++ b/examples/rp235x/src/bin/blinky_wifi.rs @@ -0,0 +1,89 @@ +//! This example tests the RP Pico 2 W onboard LED. +//! +//! It does not work with the RP Pico 2 board. See `blinky.rs`. + +#![no_std] +#![no_main] + +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +// Program metadata for `picotool info`. +// This isn't needed, but it's recommended to have these minimal entries. +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), + embassy_rp::binary_info::rp_program_description!( + c"This example tests the RP Pico 2 W's onboard LED, connected to GPIO 0 of the cyw43 \ + (WiFi chip) via PIO 0 over the SPI bus." + ), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download ../../cyw43-firmware/43439A0.bin --binary-format bin --chip RP235x --base-address 0x10100000 + // probe-rs download ../../cyw43-firmware/43439A0_clm.bin --binary-format bin --chip RP235x --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let delay = Duration::from_millis(250); + loop { + info!("led on!"); + control.gpio_set(0, true).await; + Timer::after(delay).await; + + info!("led off!"); + control.gpio_set(0, false).await; + Timer::after(delay).await; + } +} diff --git a/examples/rp235x/src/bin/blinky_wifi_pico_plus_2.rs b/examples/rp235x/src/bin/blinky_wifi_pico_plus_2.rs new file mode 100644 index 000000000..0a5bccfb3 --- /dev/null +++ b/examples/rp235x/src/bin/blinky_wifi_pico_plus_2.rs @@ -0,0 +1,88 @@ +//! This example test the Pimoroni Pico Plus 2 on board LED. +//! +//! It does not work with the RP Pico 2 board. See `blinky.rs`. + +#![no_std] +#![no_main] + +use cyw43_pio::{PioSpi, RM2_CLOCK_DIVIDER}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::{bind_interrupts, gpio}; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +// Program metadata for `picotool info`. +// This isn't needed, but it's recomended to have these minimal entries. +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), + embassy_rp::binary_info::rp_program_description!( + c"This example tests the RP Pico on board LED, connected to gpio 25" + ), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download ../../cyw43-firmware/43439A0.bin --binary-format bin --chip RP235x --base-address 0x10100000 + // probe-rs download ../../cyw43-firmware/43439A0_clm.bin --binary-format bin --chip RP235x --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 230321) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + RM2_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let delay = Duration::from_secs(1); + loop { + info!("led on!"); + control.gpio_set(0, true).await; + Timer::after(delay).await; + + info!("led off!"); + control.gpio_set(0, false).await; + Timer::after(delay).await; + } +} diff --git a/examples/rp23/src/bin/button.rs b/examples/rp235x/src/bin/button.rs similarity index 60% rename from examples/rp23/src/bin/button.rs rename to examples/rp235x/src/bin/button.rs index 0a0559397..4ad2ca3b7 100644 --- a/examples/rp23/src/bin/button.rs +++ b/examples/rp235x/src/bin/button.rs @@ -6,24 +6,9 @@ #![no_main] use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::{Input, Level, Output, Pull}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/debounce.rs b/examples/rp235x/src/bin/debounce.rs similarity index 81% rename from examples/rp23/src/bin/debounce.rs rename to examples/rp235x/src/bin/debounce.rs index e82e71f61..0077f19fc 100644 --- a/examples/rp23/src/bin/debounce.rs +++ b/examples/rp235x/src/bin/debounce.rs @@ -6,25 +6,10 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::{Input, Level, Pull}; use embassy_time::{with_deadline, Duration, Instant, Timer}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - pub struct Debouncer<'a> { input: Input<'a>, debounce: Duration, diff --git a/examples/rp23/src/bin/flash.rs b/examples/rp235x/src/bin/flash.rs similarity index 83% rename from examples/rp23/src/bin/flash.rs rename to examples/rp235x/src/bin/flash.rs index 2917dda0b..31ad4aafc 100644 --- a/examples/rp23/src/bin/flash.rs +++ b/examples/rp235x/src/bin/flash.rs @@ -1,30 +1,15 @@ -//! This example test the flash connected to the RP2040 chip. +//! This example test the flash connected to the RP2350 chip. #![no_std] #![no_main] use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::flash::{Async, ERASE_SIZE, FLASH_BASE}; use embassy_rp::peripherals::FLASH; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - const ADDR_OFFSET: u32 = 0x100000; const FLASH_SIZE: usize = 2 * 1024 * 1024; @@ -41,22 +26,13 @@ async fn main(_spawner: Spawner) { let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH0); - // Get JEDEC id - let jedec = flash.blocking_jedec_id().unwrap(); - info!("jedec id: 0x{:x}", jedec); - - // Get unique id - let mut uid = [0; 8]; - flash.blocking_unique_id(&mut uid).unwrap(); - info!("unique id: {:?}", uid); - erase_write_sector(&mut flash, 0x00); multiwrite_bytes(&mut flash, ERASE_SIZE as u32); background_read(&mut flash, (ERASE_SIZE * 2) as u32).await; - loop {} + info!("Flash Works!"); } fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { @@ -82,7 +58,7 @@ fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); info!("Contents after write starts with {=[u8]}", read_buf[0..4]); - if &read_buf[0..4] != &[0x01, 0x02, 0x03, 0x04] { + if read_buf[0..4] != [0x01, 0x02, 0x03, 0x04] { defmt::panic!("unexpected"); } } diff --git a/examples/rp23/src/bin/gpio_async.rs b/examples/rp235x/src/bin/gpio_async.rs similarity index 68% rename from examples/rp23/src/bin/gpio_async.rs rename to examples/rp235x/src/bin/gpio_async.rs index 1618f7c8b..3be8569bc 100644 --- a/examples/rp23/src/bin/gpio_async.rs +++ b/examples/rp235x/src/bin/gpio_async.rs @@ -1,4 +1,4 @@ -//! This example shows how async gpio can be used with a RP2040. +//! This example shows how async gpio can be used with a RP235x. //! //! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. @@ -7,26 +7,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio; use embassy_time::Timer; use gpio::{Input, Level, Output, Pull}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - /// It requires an external signal to be manually triggered on PIN 16. For /// example, this could be accomplished using an external power source with a /// button so that it is possible to toggle the signal from low to high. diff --git a/examples/rp23/src/bin/gpout.rs b/examples/rp235x/src/bin/gpout.rs similarity index 66% rename from examples/rp23/src/bin/gpout.rs rename to examples/rp235x/src/bin/gpout.rs index b15963f02..011359253 100644 --- a/examples/rp23/src/bin/gpout.rs +++ b/examples/rp235x/src/bin/gpout.rs @@ -7,25 +7,10 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::clocks; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/i2c_async.rs b/examples/rp235x/src/bin/i2c_async.rs similarity index 85% rename from examples/rp23/src/bin/i2c_async.rs rename to examples/rp235x/src/bin/i2c_async.rs index 2528fe1d2..e31cc894c 100644 --- a/examples/rp23/src/bin/i2c_async.rs +++ b/examples/rp235x/src/bin/i2c_async.rs @@ -9,27 +9,12 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::i2c::{self, Config, InterruptHandler}; use embassy_rp::peripherals::I2C1; use embassy_time::Timer; use embedded_hal_async::i2c::I2c; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { I2C1_IRQ => InterruptHandler; }); diff --git a/examples/rp23/src/bin/i2c_async_embassy.rs b/examples/rp235x/src/bin/i2c_async_embassy.rs similarity index 84% rename from examples/rp23/src/bin/i2c_async_embassy.rs rename to examples/rp235x/src/bin/i2c_async_embassy.rs index 461b1d171..a65b71b9f 100644 --- a/examples/rp23/src/bin/i2c_async_embassy.rs +++ b/examples/rp235x/src/bin/i2c_async_embassy.rs @@ -7,24 +7,9 @@ #![no_main] use defmt::*; -use embassy_rp::block::ImageDef; use embassy_rp::i2c::InterruptHandler; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - // Our anonymous hypotetical temperature sensor could be: // a 12-bit sensor, with 100ms startup time, range of -40*C - 125*C, and precision 0.25*C // It requires no configuration or calibration, works with all i2c bus speeds, diff --git a/examples/rp23/src/bin/i2c_blocking.rs b/examples/rp235x/src/bin/i2c_blocking.rs similarity index 80% rename from examples/rp23/src/bin/i2c_blocking.rs rename to examples/rp235x/src/bin/i2c_blocking.rs index 6d36d1890..c9c8a2760 100644 --- a/examples/rp23/src/bin/i2c_blocking.rs +++ b/examples/rp235x/src/bin/i2c_blocking.rs @@ -8,26 +8,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::i2c::{self, Config}; use embassy_time::Timer; use embedded_hal_1::i2c::I2c; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[allow(dead_code)] mod mcp23017 { pub const ADDR: u8 = 0x20; // default addr diff --git a/examples/rp23/src/bin/i2c_slave.rs b/examples/rp235x/src/bin/i2c_slave.rs similarity index 89% rename from examples/rp23/src/bin/i2c_slave.rs rename to examples/rp235x/src/bin/i2c_slave.rs index 1f3408cf3..9fffb4646 100644 --- a/examples/rp23/src/bin/i2c_slave.rs +++ b/examples/rp235x/src/bin/i2c_slave.rs @@ -4,27 +4,12 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::peripherals::{I2C0, I2C1}; use embassy_rp::{bind_interrupts, i2c, i2c_slave}; use embassy_time::Timer; use embedded_hal_async::i2c::I2c; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { I2C0_IRQ => i2c::InterruptHandler; I2C1_IRQ => i2c::InterruptHandler; diff --git a/examples/rp23/src/bin/interrupt.rs b/examples/rp235x/src/bin/interrupt.rs similarity index 86% rename from examples/rp23/src/bin/interrupt.rs rename to examples/rp235x/src/bin/interrupt.rs index 6184b1bd7..e9ac76486 100644 --- a/examples/rp23/src/bin/interrupt.rs +++ b/examples/rp235x/src/bin/interrupt.rs @@ -13,7 +13,6 @@ use core::cell::{Cell, RefCell}; use defmt::*; use embassy_executor::Spawner; use embassy_rp::adc::{self, Adc, Blocking}; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::Pull; use embassy_rp::interrupt; use embassy_rp::pwm::{Config, Pwm}; @@ -25,20 +24,6 @@ use portable_atomic::{AtomicU32, Ordering}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - static COUNTER: AtomicU32 = AtomicU32::new(0); static PWM: Mutex>> = Mutex::new(RefCell::new(None)); static ADC: Mutex, adc::Channel)>>> = @@ -47,7 +32,6 @@ static ADC_VALUES: Channel = Channel::new(); #[embassy_executor::main] async fn main(spawner: Spawner) { - embassy_rp::pac::SIO.spinlock(31).write_value(1); let p = embassy_rp::init(Default::default()); let adc = Adc::new_blocking(p.ADC, Default::default()); diff --git a/examples/rp23/src/bin/multicore.rs b/examples/rp235x/src/bin/multicore.rs similarity index 77% rename from examples/rp23/src/bin/multicore.rs rename to examples/rp235x/src/bin/multicore.rs index 8649143e1..f02dc3876 100644 --- a/examples/rp23/src/bin/multicore.rs +++ b/examples/rp235x/src/bin/multicore.rs @@ -1,4 +1,4 @@ -//! This example shows how to send messages between the two cores in the RP2040 chip. +//! This example shows how to send messages between the two cores in the RP235x chip. //! //! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. @@ -7,7 +7,6 @@ use defmt::*; use embassy_executor::Executor; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::{Level, Output}; use embassy_rp::multicore::{spawn_core1, Stack}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; @@ -16,20 +15,6 @@ use embassy_time::Timer; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - static mut CORE1_STACK: Stack<4096> = Stack::new(); static EXECUTOR0: StaticCell = StaticCell::new(); static EXECUTOR1: StaticCell = StaticCell::new(); diff --git a/examples/rp23/src/bin/multiprio.rs b/examples/rp235x/src/bin/multiprio.rs similarity index 89% rename from examples/rp23/src/bin/multiprio.rs rename to examples/rp235x/src/bin/multiprio.rs index 7590fb431..2b397f97d 100644 --- a/examples/rp23/src/bin/multiprio.rs +++ b/examples/rp235x/src/bin/multiprio.rs @@ -59,27 +59,12 @@ use cortex_m_rt::entry; use defmt::{info, unwrap}; use embassy_executor::{Executor, InterruptExecutor}; -use embassy_rp::block::ImageDef; use embassy_rp::interrupt; use embassy_rp::interrupt::{InterruptExt, Priority}; use embassy_time::{Instant, Timer, TICK_HZ}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::task] async fn run_high() { loop { diff --git a/examples/rp235x/src/bin/otp.rs b/examples/rp235x/src/bin/otp.rs new file mode 100644 index 000000000..5ffbb7610 --- /dev/null +++ b/examples/rp235x/src/bin/otp.rs @@ -0,0 +1,31 @@ +//! This example shows reading the OTP constants on the RP235x. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::otp; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _ = embassy_rp::init(Default::default()); + // + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let chip_id = unwrap!(otp::get_chipid()); + info!("Unique id:{:X}", chip_id); + + let private_rand = unwrap!(otp::get_private_random_number()); + info!("Private Rand:{:X}", private_rand); + + loop { + Timer::after_secs(1).await; + } +} diff --git a/examples/rp235x/src/bin/overclock.rs b/examples/rp235x/src/bin/overclock.rs new file mode 100644 index 000000000..5fd97ef97 --- /dev/null +++ b/examples/rp235x/src/bin/overclock.rs @@ -0,0 +1,74 @@ +//! # Overclocking the RP2350 to 200 MHz +//! +//! This example demonstrates how to configure the RP2350 to run at 200 MHz instead of the default 150 MHz. +//! +//! ## Note +//! +//! As of yet there is no official support for running the RP235x at higher clock frequencies and/or other core voltages than the default. +//! Doing so may cause unexpected behavior and/or damage the chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig, CoreVoltage}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Set up for clock frequency of 200 MHz, setting all necessary defaults. + let mut config = Config::new(ClockConfig::system_freq(200_000_000).unwrap()); + + // since for the rp235x there is no official support for higher clock frequencies, `system_freq()` will not set a voltage for us. + // We need to guess the core voltage, that is needed for the higher clock frequency. Going with a small increase from the default 1.1V here, based on + // what we know about the RP2040. This is not guaranteed to be correct. + config.clocks.core_voltage = CoreVoltage::V1_15; + + // Initialize the peripherals + let p = embassy_rp::init(config); + + // Show CPU frequency for verification + let sys_freq = clk_sys_freq(); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); + // Show core voltage for verification + let core_voltage = core_voltage().unwrap(); + info!("Core voltage: {}", core_voltage); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = Instant::now() - start; + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + counter, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/examples/rp23/src/bin/pio_async.rs b/examples/rp235x/src/bin/pio_async.rs similarity index 81% rename from examples/rp23/src/bin/pio_async.rs rename to examples/rp235x/src/bin/pio_async.rs index 005708bc2..a519b8a50 100644 --- a/examples/rp23/src/bin/pio_async.rs +++ b/examples/rp235x/src/bin/pio_async.rs @@ -1,40 +1,26 @@ -//! This example shows powerful PIO module in the RP2040 chip. +//! This example shows powerful PIO module in the RP235x chip. #![no_std] #![no_main] use defmt::info; use embassy_executor::Spawner; -use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::{bind_interrupts, Peri}; use fixed::traits::ToFixed; use fixed_macro::types::U56F8; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) { +fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: Peri<'a, impl PioPin>) { // Setup sm0 // Send data serially to pin - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( ".origin 16", "set pindirs, 1", ".wrap_target", @@ -68,7 +54,7 @@ fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, // Setupm sm1 // Read 0b10101 repeatedly until ISR is full - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( // ".origin 8", "set x, 0x15", @@ -98,7 +84,7 @@ fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, // Setup sm2 // Repeatedly trigger IRQ 3 - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( ".origin 0", ".wrap_target", "set x,10", diff --git a/examples/rp23/src/bin/pio_dma.rs b/examples/rp235x/src/bin/pio_dma.rs similarity index 69% rename from examples/rp23/src/bin/pio_dma.rs rename to examples/rp235x/src/bin/pio_dma.rs index 48fd9123f..17332a238 100644 --- a/examples/rp23/src/bin/pio_dma.rs +++ b/examples/rp235x/src/bin/pio_dma.rs @@ -1,32 +1,18 @@ -//! This example shows powerful PIO module in the RP2040 chip. +//! This example shows powerful PIO module in the RP235x chip. #![no_std] #![no_main] use defmt::info; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_rp::block::ImageDef; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; -use embassy_rp::{bind_interrupts, Peripheral}; use fixed::traits::ToFixed; use fixed_macro::types::U56F8; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); @@ -47,7 +33,7 @@ async fn main(_spawner: Spawner) { .. } = Pio::new(pio, Irqs); - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( ".origin 0", "set pindirs,1", ".wrap_target", @@ -76,8 +62,8 @@ async fn main(_spawner: Spawner) { sm.set_config(&cfg); sm.set_enable(true); - let mut dma_out_ref = p.DMA_CH0.into_ref(); - let mut dma_in_ref = p.DMA_CH1.into_ref(); + let mut dma_out_ref = p.DMA_CH0; + let mut dma_in_ref = p.DMA_CH1; let mut dout = [0x12345678u32; 29]; for i in 1..dout.len() { dout[i] = (dout[i - 1] & 0x0fff_ffff) * 13 + 7; @@ -86,8 +72,8 @@ async fn main(_spawner: Spawner) { loop { let (rx, tx) = sm.rx_tx(); join( - tx.dma_push(dma_out_ref.reborrow(), &dout), - rx.dma_pull(dma_in_ref.reborrow(), &mut din), + tx.dma_push(dma_out_ref.reborrow(), &dout, false), + rx.dma_pull(dma_in_ref.reborrow(), &mut din, false), ) .await; for i in 0..din.len() { diff --git a/examples/rp235x/src/bin/pio_hd44780.rs b/examples/rp235x/src/bin/pio_hd44780.rs new file mode 100644 index 000000000..06d989505 --- /dev/null +++ b/examples/rp235x/src/bin/pio_hd44780.rs @@ -0,0 +1,87 @@ +//! This example shows powerful PIO module in the RP235x chip to communicate with a HD44780 display. +//! See (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::hd44780::{PioHD44780, PioHD44780CommandSequenceProgram, PioHD44780CommandWordProgram}; +use embassy_rp::pwm::{self, Pwm}; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(pub struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // this test assumes a 2x16 HD44780 display attached as follow: + // rs = PIN0 + // rw = PIN1 + // e = PIN2 + // db4 = PIN3 + // db5 = PIN4 + // db6 = PIN5 + // db7 = PIN6 + // additionally a pwm signal for a bias voltage charge pump is provided on pin 15, + // allowing direct connection of the display to the RP235x without level shifters. + let p = embassy_rp::init(Default::default()); + + let _pwm = Pwm::new_output_b(p.PWM_SLICE7, p.PIN_15, { + let mut c = pwm::Config::default(); + c.divider = 125.into(); + c.top = 100; + c.compare_b = 50; + c + }); + + let Pio { + mut common, sm0, irq0, .. + } = Pio::new(p.PIO0, Irqs); + + let word_prg = PioHD44780CommandWordProgram::new(&mut common); + let seq_prg = PioHD44780CommandSequenceProgram::new(&mut common); + + let mut hd = PioHD44780::new( + &mut common, + sm0, + irq0, + p.DMA_CH3, + p.PIN_0, + p.PIN_1, + p.PIN_2, + p.PIN_3, + p.PIN_4, + p.PIN_5, + p.PIN_6, + &word_prg, + &seq_prg, + ) + .await; + + loop { + struct Buf([u8; N], usize); + impl Write for Buf { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + for b in s.as_bytes() { + if self.1 >= N { + return Err(core::fmt::Error); + } + self.0[self.1] = *b; + self.1 += 1; + } + Ok(()) + } + } + let mut buf = Buf([0; 16], 0); + write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap(); + hd.add_line(&buf.0[0..buf.1]).await; + Timer::after_secs(1).await; + } +} diff --git a/examples/rp235x/src/bin/pio_i2s.rs b/examples/rp235x/src/bin/pio_i2s.rs new file mode 100644 index 000000000..cfcb0221d --- /dev/null +++ b/examples/rp235x/src/bin/pio_i2s.rs @@ -0,0 +1,93 @@ +//! This example shows generating audio and sending it to a connected i2s DAC using the PIO +//! module of the RP235x. +//! +//! 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::bind_interrupts; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::i2s::{PioI2sOut, PioI2sOutProgram}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const SAMPLE_RATE: u32 = 48_000; +const BIT_DEPTH: u32 = 16; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Setup pio state machine for i2s output + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + let bit_clock_pin = p.PIN_18; + let left_right_clock_pin = p.PIN_19; + let data_pin = p.PIN_20; + + let program = PioI2sOutProgram::new(&mut common); + let mut i2s = PioI2sOut::new( + &mut common, + sm0, + p.DMA_CH0, + data_pin, + bit_clock_pin, + left_right_clock_pin, + SAMPLE_RATE, + BIT_DEPTH, + &program, + ); + + let fade_input = Input::new(p.PIN_0, Pull::Up); + + // 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 + 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 = i2s.write(front_buffer); + + // fade in audio when bootsel is pressed + let fade_target = if fade_input.is_low() { 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/rp235x/src/bin/pio_onewire.rs b/examples/rp235x/src/bin/pio_onewire.rs new file mode 100644 index 000000000..991510851 --- /dev/null +++ b/examples/rp235x/src/bin/pio_onewire.rs @@ -0,0 +1,83 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, InterruptHandler, Pio}; +use embassy_rp::pio_programs::onewire::{PioOneWire, PioOneWireProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + + let prg = PioOneWireProgram::new(&mut pio.common); + let onewire = PioOneWire::new(&mut pio.common, pio.sm0, p.PIN_2, &prg); + + let mut sensor = Ds18b20::new(onewire); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + wire: PioOneWire<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + pub fn new(wire: PioOneWire<'d, PIO, SM>) -> Self { + Self { wire } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.wire.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.wire.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.wire.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/examples/rp235x/src/bin/pio_pwm.rs b/examples/rp235x/src/bin/pio_pwm.rs new file mode 100644 index 000000000..5712b5b91 --- /dev/null +++ b/examples/rp235x/src/bin/pio_pwm.rs @@ -0,0 +1,38 @@ +//! This example shows how to create a pwm using the PIO module in the RP235x chip. + +#![no_std] +#![no_main] +use core::time::Duration; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const REFRESH_INTERVAL: u64 = 20000; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // Note that PIN_25 is the led pin on the Pico + let prg = PioPwmProgram::new(&mut common); + let mut pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_25, &prg); + pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); + pwm_pio.start(); + + let mut duration = 0; + loop { + duration = (duration + 1) % 1000; + pwm_pio.write(Duration::from_micros(duration)); + Timer::after_millis(1).await; + } +} diff --git a/examples/rp235x/src/bin/pio_rotary_encoder.rs b/examples/rp235x/src/bin/pio_rotary_encoder.rs new file mode 100644 index 000000000..e820d316d --- /dev/null +++ b/examples/rp235x/src/bin/pio_rotary_encoder.rs @@ -0,0 +1,55 @@ +//! This example shows how to use the PIO module in the RP235x to read a quadrature rotary encoder. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::rotary_encoder::{Direction, PioEncoder, PioEncoderProgram}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn encoder_0(mut encoder: PioEncoder<'static, PIO0, 0>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::task] +async fn encoder_1(mut encoder: PioEncoder<'static, PIO0, 1>) { + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, sm0, sm1, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioEncoderProgram::new(&mut common); + let encoder0 = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5, &prg); + let encoder1 = PioEncoder::new(&mut common, sm1, p.PIN_6, p.PIN_7, &prg); + + spawner.must_spawn(encoder_0(encoder0)); + spawner.must_spawn(encoder_1(encoder1)); +} diff --git a/examples/rp23/src/bin/pio_rotary_encoder.rs b/examples/rp235x/src/bin/pio_rotary_encoder_rxf.rs similarity index 52% rename from examples/rp23/src/bin/pio_rotary_encoder.rs rename to examples/rp235x/src/bin/pio_rotary_encoder_rxf.rs index 287992a83..61af94560 100644 --- a/examples/rp23/src/bin/pio_rotary_encoder.rs +++ b/examples/rp235x/src/bin/pio_rotary_encoder_rxf.rs @@ -1,30 +1,28 @@ -//! This example shows how to use the PIO module in the RP2040 to read a quadrature rotary encoder. +//! This example shows how to use the PIO module in the RP235x to read a quadrature rotary encoder. +//! It differs from the other example in that it uses the RX FIFO as a status register #![no_std] #![no_main] use defmt::info; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::Pull; use embassy_rp::peripherals::PIO0; -use embassy_rp::{bind_interrupts, pio}; +use embassy_rp::pio::program::pio_asm; +use embassy_rp::{bind_interrupts, pio, Peri}; +use embassy_time::Timer; use fixed::traits::ToFixed; use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - // Program metadata for `picotool info` -#[link_section = ".bi_entries"] +#[unsafe(link_section = ".bi_entries")] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), + embassy_rp::binary_info::rp_program_name!(c"example_pio_rotary_encoder_rxf"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Rotary encoder (RXF)"), + embassy_rp::binary_info::rp_program_build_attribute!(), ]; bind_interrupts!(struct Irqs { @@ -39,36 +37,59 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { pub fn new( pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, - pin_a: impl PioPin, - pin_b: impl PioPin, + pin_a: Peri<'d, impl PioPin>, + pin_b: Peri<'d, impl PioPin>, ) -> Self { let mut pin_a = pio.make_pio_pin(pin_a); let mut pin_b = pio.make_pio_pin(pin_b); pin_a.set_pull(Pull::Up); pin_b.set_pull(Pull::Up); + sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); - let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); + let prg = pio_asm!( + "start:" + // encoder count is stored in X + "mov isr, x" + // and then moved to the RX FIFO register + "mov rxfifo[0], isr" + + // wait for encoder transition + "wait 1 pin 1" + "wait 0 pin 1" + + "set y, 0" + "mov y, pins[1]" + + // update X depending on pin 1 + "jmp !y decr" + + // this is just a clever way of doing x++ + "mov x, ~x" + "jmp x--, incr" + "incr:" + "mov x, ~x" + "jmp start" + + // and this is x-- + "decr:" + "jmp x--, start" + ); let mut cfg = Config::default(); cfg.set_in_pins(&[&pin_a, &pin_b]); - cfg.fifo_join = FifoJoin::RxOnly; + cfg.fifo_join = FifoJoin::RxAsStatus; cfg.shift_in.direction = ShiftDirection::Left; cfg.clock_divider = 10_000.to_fixed(); cfg.use_program(&pio.load_program(&prg.program), &[]); sm.set_config(&cfg); + sm.set_enable(true); Self { sm } } - pub async fn read(&mut self) -> Direction { - loop { - match self.sm.rx().wait_pull().await { - 0 => return Direction::CounterClockwise, - 1 => return Direction::Clockwise, - _ => {} - } - } + pub async fn read(&mut self) -> i32 { + self.sm.get_rxf_entry(0) as i32 } } @@ -84,12 +105,8 @@ async fn main(_spawner: Spawner) { let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); - let mut count = 0; loop { - info!("Count: {}", count); - count += match encoder.read().await { - Direction::Clockwise => 1, - Direction::CounterClockwise => -1, - }; + info!("Count: {}", encoder.read().await); + Timer::after_millis(1000).await; } } diff --git a/examples/rp23/src/bin/pio_servo.rs b/examples/rp235x/src/bin/pio_servo.rs similarity index 53% rename from examples/rp23/src/bin/pio_servo.rs rename to examples/rp235x/src/bin/pio_servo.rs index 1dec86927..086b02f03 100644 --- a/examples/rp23/src/bin/pio_servo.rs +++ b/examples/rp235x/src/bin/pio_servo.rs @@ -1,33 +1,17 @@ -//! This example shows how to create a pwm using the PIO module in the RP2040 chip. +//! This example shows how to create a pwm using the PIO module in the RP235x chip. #![no_std] #![no_main] use core::time::Duration; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; -use embassy_rp::gpio::Level; +use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; -use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; -use embassy_rp::{bind_interrupts, clocks}; +use embassy_rp::pio::{Instance, InterruptHandler, Pio}; +use embassy_rp::pio_programs::pwm::{PioPwm, PioPwmProgram}; use embassy_time::Timer; -use pio::InstructionOperands; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical @@ -37,88 +21,8 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -pub fn to_pio_cycles(duration: Duration) -> u32 { - (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow -} - -pub struct PwmPio<'d, T: Instance, const SM: usize> { - sm: StateMachine<'d, T, SM>, -} - -impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { - pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { - let prg = pio_proc::pio_asm!( - ".side_set 1 opt" - "pull noblock side 0" - "mov x, osr" - "mov y, isr" - "countloop:" - "jmp x!=y noset" - "jmp skip side 1" - "noset:" - "nop" - "skip:" - "jmp y-- countloop" - ); - - pio.load_program(&prg.program); - let pin = pio.make_pio_pin(pin); - sm.set_pins(Level::High, &[&pin]); - sm.set_pin_dirs(Direction::Out, &[&pin]); - - let mut cfg = Config::default(); - cfg.use_program(&pio.load_program(&prg.program), &[&pin]); - - sm.set_config(&cfg); - - Self { sm } - } - - pub fn start(&mut self) { - self.sm.set_enable(true); - } - - pub fn stop(&mut self) { - self.sm.set_enable(false); - } - - pub fn set_period(&mut self, duration: Duration) { - let is_enabled = self.sm.is_enabled(); - while !self.sm.tx().empty() {} // Make sure that the queue is empty - self.sm.set_enable(false); - self.sm.tx().push(to_pio_cycles(duration)); - unsafe { - self.sm.exec_instr( - InstructionOperands::PULL { - if_empty: false, - block: false, - } - .encode(), - ); - self.sm.exec_instr( - InstructionOperands::OUT { - destination: ::pio::OutDestination::ISR, - bit_count: 32, - } - .encode(), - ); - }; - if is_enabled { - self.sm.set_enable(true) // Enable if previously enabled - } - } - - pub fn set_level(&mut self, level: u32) { - self.sm.tx().push(level); - } - - pub fn write(&mut self, duration: Duration) { - self.set_level(to_pio_cycles(duration)); - } -} - pub struct ServoBuilder<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, period: Duration, min_pulse_width: Duration, max_pulse_width: Duration, @@ -126,7 +30,7 @@ pub struct ServoBuilder<'d, T: Instance, const SM: usize> { } impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { - pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { + pub fn new(pwm: PioPwm<'d, T, SM>) -> Self { Self { pwm, period: Duration::from_micros(REFRESH_INTERVAL), @@ -168,7 +72,7 @@ impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { } pub struct Servo<'d, T: Instance, const SM: usize> { - pwm: PwmPio<'d, T, SM>, + pwm: PioPwm<'d, T, SM>, min_pulse_width: Duration, max_pulse_width: Duration, max_degree_rotation: u64, @@ -205,7 +109,8 @@ async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); - let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); + let prg = PioPwmProgram::new(&mut common); + let pwm_pio = PioPwm::new(&mut common, sm0, p.PIN_1, &prg); let mut servo = ServoBuilder::new(pwm_pio) .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. diff --git a/examples/rp235x/src/bin/pio_stepper.rs b/examples/rp235x/src/bin/pio_stepper.rs new file mode 100644 index 000000000..931adbeda --- /dev/null +++ b/examples/rp235x/src/bin/pio_stepper.rs @@ -0,0 +1,49 @@ +//! This example shows how to use the PIO module in the RP235x to implement a stepper motor driver +//! for a 5-wire stepper such as the 28BYJ-48. You can halt an ongoing rotation by dropping the future. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::stepper::{PioStepper, PioStepperProgram}; +use embassy_time::{with_timeout, Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, irq0, sm0, .. + } = Pio::new(p.PIO0, Irqs); + + let prg = PioStepperProgram::new(&mut common); + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7, &prg); + stepper.set_frequency(120); + loop { + info!("CW full steps"); + stepper.step(1000).await; + + info!("CCW full steps, drop after 1 sec"); + if with_timeout(Duration::from_secs(1), stepper.step(-i32::MAX)) + .await + .is_err() + { + info!("Time's up!"); + Timer::after(Duration::from_secs(1)).await; + } + + info!("CW half steps"); + stepper.step_half(1000).await; + + info!("CCW half steps"); + stepper.step_half(-1000).await; + } +} diff --git a/examples/rp235x/src/bin/pio_uart.rs b/examples/rp235x/src/bin/pio_uart.rs new file mode 100644 index 000000000..d92e33feb --- /dev/null +++ b/examples/rp235x/src/bin/pio_uart.rs @@ -0,0 +1,190 @@ +//! This example shows how to use the PIO module in the RP235x chip to implement a duplex UART. +//! The PIO module is a very powerful peripheral that can be used to implement many different +//! protocols. It is a very flexible state machine that can be programmed to do almost anything. +//! +//! This example opens up a USB device that implements a CDC ACM serial port. It then uses the +//! PIO module to implement a UART that is connected to the USB serial port. This allows you to +//! communicate with a device connected to the RP235x over USB serial. + +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] + +use defmt::{info, panic, trace}; +use embassy_executor::Spawner; +use embassy_futures::join::{join, join3}; +use embassy_rp::peripherals::{PIO0, USB}; +use embassy_rp::pio_programs::uart::{PioUartRx, PioUartRxProgram, PioUartTx, PioUartTxProgram}; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_rp::{bind_interrupts, pio}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use embedded_io_async::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; + PIO0_IRQ_0 => pio::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("PIO UART 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 config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // PIO UART setup + let pio::Pio { + mut common, sm0, sm1, .. + } = pio::Pio::new(p.PIO0, Irqs); + + let tx_program = PioUartTxProgram::new(&mut common); + let mut uart_tx = PioUartTx::new(9600, &mut common, sm0, p.PIN_4, &tx_program); + + let rx_program = PioUartRxProgram::new(&mut common); + let mut uart_rx = PioUartRx::new(9600, &mut common, sm1, p.PIN_5, &rx_program); + + // Pipe setup + let mut usb_pipe: Pipe = Pipe::new(); + let (mut usb_pipe_reader, mut usb_pipe_writer) = usb_pipe.split(); + + let mut uart_pipe: Pipe = Pipe::new(); + let (mut uart_pipe_reader, mut uart_pipe_writer) = uart_pipe.split(); + + let (mut usb_tx, mut usb_rx) = class.split(); + + // Read + write from USB + let usb_future = async { + loop { + info!("Wait for USB connection"); + usb_rx.wait_connection().await; + info!("Connected"); + let _ = join( + usb_read(&mut usb_rx, &mut uart_pipe_writer), + usb_write(&mut usb_tx, &mut usb_pipe_reader), + ) + .await; + info!("Disconnected"); + } + }; + + // Read + write from UART + let uart_future = join( + uart_read(&mut uart_rx, &mut usb_pipe_writer), + uart_write(&mut uart_tx, &mut uart_pipe_reader), + ); + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join3(usb_fut, usb_future, uart_future).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Read from the USB and write it to the UART TX pipe +async fn usb_read<'d, T: Instance + 'd>( + usb_rx: &mut Receiver<'d, Driver<'d, T>>, + uart_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = usb_rx.read_packet(&mut buf).await?; + let data = &buf[..n]; + trace!("USB IN: {:x}", data); + (*uart_pipe_writer).write(data).await; + } +} + +/// Read from the USB TX pipe and write it to the USB +async fn usb_write<'d, T: Instance + 'd>( + usb_tx: &mut Sender<'d, Driver<'d, T>>, + usb_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = (*usb_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("USB OUT: {:x}", data); + usb_tx.write_packet(&data).await?; + } +} + +/// Read from the UART and write it to the USB TX pipe +async fn uart_read( + uart_rx: &mut PioUartRx<'_, PIO, SM>, + usb_pipe_writer: &mut embassy_sync::pipe::Writer<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = uart_rx.read(&mut buf).await.expect("UART read error"); + if n == 0 { + continue; + } + let data = &buf[..n]; + trace!("UART IN: {:x}", buf); + (*usb_pipe_writer).write(data).await; + } +} + +/// Read from the UART TX pipe and write it to the UART +async fn uart_write( + uart_tx: &mut PioUartTx<'_, PIO, SM>, + uart_pipe_reader: &mut embassy_sync::pipe::Reader<'_, NoopRawMutex, 20>, +) -> ! { + let mut buf = [0; 64]; + loop { + let n = (*uart_pipe_reader).read(&mut buf).await; + let data = &buf[..n]; + trace!("UART OUT: {:x}", data); + let _ = uart_tx.write(&data).await; + } +} diff --git a/examples/rp235x/src/bin/pio_ws2812.rs b/examples/rp235x/src/bin/pio_ws2812.rs new file mode 100644 index 000000000..42694c527 --- /dev/null +++ b/examples/rp235x/src/bin/pio_ws2812.rs @@ -0,0 +1,68 @@ +//! This example shows powerful PIO module in the RP235x chip to communicate with WS2812 LED modules. +//! See (https://www.sparkfun.com/categories/tags/ws2812) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::pio_programs::ws2812::{PioWs2812, PioWs2812Program}; +use embassy_time::{Duration, Ticker}; +use smart_leds::RGB8; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +/// Input a value 0 to 255 to get a color value +/// The colours are a transition r - g - b - back to r. +fn wheel(mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + let p = embassy_rp::init(Default::default()); + + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit + // feather boards for the 2040 both have one built in. + const NUM_LEDS: usize = 1; + let mut data = [RGB8::default(); NUM_LEDS]; + + // Common neopixel pins: + // Thing plus: 8 + // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 + let program = PioWs2812Program::new(&mut common); + let mut ws2812 = PioWs2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16, &program); + + // 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:"); + for i in 0..NUM_LEDS { + data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); + debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); + } + ws2812.write(&data).await; + + ticker.next().await; + } + } +} diff --git a/examples/rp235x/src/bin/pwm.rs b/examples/rp235x/src/bin/pwm.rs new file mode 100644 index 000000000..da1acc18a --- /dev/null +++ b/examples/rp235x/src/bin/pwm.rs @@ -0,0 +1,80 @@ +//! This example shows how to use PWM (Pulse Width Modulation) in the RP235x chip. +//! +//! We demonstrate two ways of using PWM: +//! 1. Via config +//! 2. Via setting a duty cycle + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::peripherals::{PIN_25, PIN_4, PWM_SLICE2, PWM_SLICE4}; +use embassy_rp::pwm::{Config, Pwm, SetDutyCycle}; +use embassy_rp::Peri; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + spawner.spawn(pwm_set_config(p.PWM_SLICE4, p.PIN_25)).unwrap(); + spawner.spawn(pwm_set_dutycycle(p.PWM_SLICE2, p.PIN_4)).unwrap(); +} + +/// Demonstrate PWM by modifying & applying the config +/// +/// Using the onboard led, if You are using a different Board than plain Pico2 (i.e. W variant) +/// you must use another slice & pin and an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_config(slice4: Peri<'static, PWM_SLICE4>, pin25: Peri<'static, PIN_25>) { + let mut c = Config::default(); + c.top = 32_768; + c.compare_b = 8; + let mut pwm = Pwm::new_output_b(slice4, pin25, c.clone()); + + loop { + info!("current LED duty cycle: {}/32768", c.compare_b); + Timer::after_secs(1).await; + c.compare_b = c.compare_b.rotate_left(4); + pwm.set_config(&c); + } +} + +/// Demonstrate PWM by setting duty cycle +/// +/// Using GP4 in Slice2, make sure to use an appropriate resistor. +#[embassy_executor::task] +async fn pwm_set_dutycycle(slice2: Peri<'static, PWM_SLICE2>, pin4: Peri<'static, PIN_4>) { + // If we aim for a specific frequency, here is how we can calculate the top value. + // The top value sets the period of the PWM cycle, so a counter goes from 0 to top and then wraps around to 0. + // Every such wraparound is one PWM cycle. So here is how we get 25KHz: + let desired_freq_hz = 25_000; + let clock_freq_hz = embassy_rp::clocks::clk_sys_freq(); + let divider = 16u8; + let period = (clock_freq_hz / (desired_freq_hz * divider as u32)) as u16 - 1; + + let mut c = Config::default(); + c.top = period; + c.divider = divider.into(); + + let mut pwm = Pwm::new_output_a(slice2, pin4, c.clone()); + + loop { + // 100% duty cycle, fully on + pwm.set_duty_cycle_fully_on().unwrap(); + Timer::after_secs(1).await; + + // 66% duty cycle. Expressed as simple percentage. + pwm.set_duty_cycle_percent(66).unwrap(); + Timer::after_secs(1).await; + + // 25% duty cycle. Expressed as 32768/4 = 8192. + pwm.set_duty_cycle(c.top / 4).unwrap(); + Timer::after_secs(1).await; + + // 0% duty cycle, fully off. + pwm.set_duty_cycle_fully_off().unwrap(); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/pwm_input.rs b/examples/rp235x/src/bin/pwm_input.rs similarity index 59% rename from examples/rp23/src/bin/pwm_input.rs rename to examples/rp235x/src/bin/pwm_input.rs index b75d04963..bf454a936 100644 --- a/examples/rp23/src/bin/pwm_input.rs +++ b/examples/rp235x/src/bin/pwm_input.rs @@ -5,26 +5,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::Pull; use embassy_rp::pwm::{Config, InputMode, Pwm}; use embassy_time::{Duration, Ticker}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp235x/src/bin/pwm_tb6612fng_motor_driver.rs b/examples/rp235x/src/bin/pwm_tb6612fng_motor_driver.rs new file mode 100644 index 000000000..2cfb2038d --- /dev/null +++ b/examples/rp235x/src/bin/pwm_tb6612fng_motor_driver.rs @@ -0,0 +1,105 @@ +//! # PWM TB6612FNG motor driver +//! +//! This example shows the use of a TB6612FNG motor driver. The driver is built on top of embedded_hal and the example demonstrates how embassy_rp can be used to interact with ist. + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::config::Config; +use embassy_rp::gpio::Output; +use embassy_rp::{gpio, peripherals, pwm, Peri}; +use embassy_time::{Duration, Timer}; +use tb6612fng::{DriveCommand, Motor, Tb6612fng}; +use {defmt_rtt as _, panic_probe as _}; + +assign_resources! { + motor: MotorResources { + standby_pin: PIN_22, + left_slice: PWM_SLICE6, + left_pwm_pin: PIN_28, + left_forward_pin: PIN_21, + left_backward_pin: PIN_20, + right_slice: PWM_SLICE5, + right_pwm_pin: PIN_27, + right_forward_pin: PIN_19, + right_backward_pin: PIN_18, + }, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Config::default()); + let s = split_resources!(p); + let r = s.motor; + + // we want a PWM frequency of 10KHz, especially cheaper motors do not respond well to higher frequencies + let desired_freq_hz = 10_000; + let clock_freq_hz = embassy_rp::clocks::clk_sys_freq(); + let divider = 16u8; + let period = (clock_freq_hz / (desired_freq_hz * divider as u32)) as u16 - 1; + + // we need a standby output and two motors to construct a full TB6612FNG + + // standby pin + let stby = Output::new(r.standby_pin, gpio::Level::Low); + + // motor A, here defined to be the left motor + let left_fwd = gpio::Output::new(r.left_forward_pin, gpio::Level::Low); + let left_bckw = gpio::Output::new(r.left_backward_pin, gpio::Level::Low); + let mut left_speed = pwm::Config::default(); + left_speed.top = period; + left_speed.divider = divider.into(); + let left_pwm = pwm::Pwm::new_output_a(r.left_slice, r.left_pwm_pin, left_speed); + let left_motor = Motor::new(left_fwd, left_bckw, left_pwm).unwrap(); + + // motor B, here defined to be the right motor + let right_fwd = gpio::Output::new(r.right_forward_pin, gpio::Level::Low); + let right_bckw = gpio::Output::new(r.right_backward_pin, gpio::Level::Low); + let mut right_speed = pwm::Config::default(); + right_speed.top = period; + right_speed.divider = divider.into(); + let right_pwm = pwm::Pwm::new_output_b(r.right_slice, r.right_pwm_pin, right_speed); + let right_motor = Motor::new(right_fwd, right_bckw, right_pwm).unwrap(); + + // construct the motor driver + let mut control = Tb6612fng::new(left_motor, right_motor, stby).unwrap(); + + loop { + // wake up the motor driver + info!("end standby"); + control.disable_standby().unwrap(); + Timer::after(Duration::from_millis(100)).await; + + // drive a straight line forward at 20% speed for 5s + info!("drive straight"); + control.motor_a.drive(DriveCommand::Forward(80)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(80)).unwrap(); + Timer::after(Duration::from_secs(5)).await; + + // coast for 2s + info!("coast"); + control.motor_a.drive(DriveCommand::Stop).unwrap(); + control.motor_b.drive(DriveCommand::Stop).unwrap(); + Timer::after(Duration::from_secs(2)).await; + + // actively brake + info!("brake"); + control.motor_a.drive(DriveCommand::Brake).unwrap(); + control.motor_b.drive(DriveCommand::Brake).unwrap(); + Timer::after(Duration::from_secs(1)).await; + + // slowly turn for 3s + info!("turn"); + control.motor_a.drive(DriveCommand::Backward(50)).unwrap(); + control.motor_b.drive(DriveCommand::Forward(50)).unwrap(); + Timer::after(Duration::from_secs(3)).await; + + // and put the driver in standby mode and wait for 5s + info!("standby"); + control.enable_standby().unwrap(); + Timer::after(Duration::from_secs(5)).await; + } +} diff --git a/examples/rp23/src/bin/rosc.rs b/examples/rp235x/src/bin/rosc.rs similarity index 60% rename from examples/rp23/src/bin/rosc.rs rename to examples/rp235x/src/bin/rosc.rs index 28c778f51..942b72319 100644 --- a/examples/rp23/src/bin/rosc.rs +++ b/examples/rp235x/src/bin/rosc.rs @@ -7,26 +7,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::{clocks, gpio}; use embassy_time::Timer; use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = embassy_rp::config::Config::default(); diff --git a/examples/rp23/src/bin/shared_bus.rs b/examples/rp235x/src/bin/shared_bus.rs similarity index 83% rename from examples/rp23/src/bin/shared_bus.rs rename to examples/rp235x/src/bin/shared_bus.rs index 00e65f80d..9267dfccb 100644 --- a/examples/rp23/src/bin/shared_bus.rs +++ b/examples/rp235x/src/bin/shared_bus.rs @@ -8,8 +8,7 @@ use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; -use embassy_rp::gpio::{AnyPin, Level, Output}; +use embassy_rp::gpio::{Level, Output}; use embassy_rp::i2c::{self, I2c, InterruptHandler}; use embassy_rp::peripherals::{I2C1, SPI1}; use embassy_rp::spi::{self, Spi}; @@ -19,20 +18,6 @@ use embassy_time::Timer; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - type Spi1Bus = Mutex>; type I2c1Bus = Mutex>; @@ -60,8 +45,8 @@ async fn main(spawner: Spawner) { let spi_bus = SPI_BUS.init(Mutex::new(spi)); // Chip select pins for the SPI devices - let cs_a = Output::new(AnyPin::from(p.PIN_0), Level::High); - let cs_b = Output::new(AnyPin::from(p.PIN_1), Level::High); + let cs_a = Output::new(p.PIN_0, Level::High); + let cs_b = Output::new(p.PIN_1, Level::High); spawner.must_spawn(spi_task_a(spi_bus, cs_a)); spawner.must_spawn(spi_task_b(spi_bus, cs_b)); diff --git a/examples/rp23/src/bin/sharing.rs b/examples/rp235x/src/bin/sharing.rs similarity index 90% rename from examples/rp23/src/bin/sharing.rs rename to examples/rp235x/src/bin/sharing.rs index b5ef08147..856be6ace 100644 --- a/examples/rp23/src/bin/sharing.rs +++ b/examples/rp235x/src/bin/sharing.rs @@ -19,7 +19,6 @@ use core::sync::atomic::{AtomicU32, Ordering}; use cortex_m_rt::entry; use defmt::info; use embassy_executor::{Executor, InterruptExecutor}; -use embassy_rp::block::ImageDef; use embassy_rp::clocks::RoscRng; use embassy_rp::interrupt::{InterruptExt, Priority}; use embassy_rp::peripherals::UART0; @@ -28,25 +27,10 @@ use embassy_rp::{bind_interrupts, interrupt}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::{blocking_mutex, mutex}; use embassy_time::{Duration, Ticker}; -use rand::RngCore; use static_cell::{ConstStaticCell, StaticCell}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - -type UartAsyncMutex = mutex::Mutex>; +type UartAsyncMutex = mutex::Mutex>; struct MyType { inner: u32, diff --git a/examples/rp23/src/bin/spi.rs b/examples/rp235x/src/bin/spi.rs similarity index 69% rename from examples/rp23/src/bin/spi.rs rename to examples/rp235x/src/bin/spi.rs index 98aa7622c..308f05c01 100644 --- a/examples/rp23/src/bin/spi.rs +++ b/examples/rp235x/src/bin/spi.rs @@ -1,4 +1,4 @@ -//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP235x chip. //! //! Example for resistive touch sensor in Waveshare Pico-ResTouch @@ -7,26 +7,11 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::spi::Spi; use embassy_rp::{gpio, spi}; use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/spi_async.rs b/examples/rp235x/src/bin/spi_async.rs similarity index 62% rename from examples/rp23/src/bin/spi_async.rs rename to examples/rp235x/src/bin/spi_async.rs index 71eaa5c05..62bedc68a 100644 --- a/examples/rp23/src/bin/spi_async.rs +++ b/examples/rp235x/src/bin/spi_async.rs @@ -1,4 +1,4 @@ -//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP235x chip. //! No specific hardware is specified in this example. If you connect pin 11 and 12 you should get the same data back. #![no_std] @@ -6,25 +6,10 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::spi::{Config, Spi}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp235x/src/bin/spi_display.rs b/examples/rp235x/src/bin/spi_display.rs new file mode 100644 index 000000000..9967abefd --- /dev/null +++ b/examples/rp235x/src/bin/spi_display.rs @@ -0,0 +1,177 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2350 chip. +//! +//! Example written for a display using the ST7789 chip. Possibly the Waveshare Pico-ResTouch +//! (https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8) + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::*; +use display_interface_spi::SPIInterface; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::spi; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Delay; +use embedded_graphics::image::{Image, ImageRawLE}; +use embedded_graphics::mono_font::ascii::FONT_10X20; +use embedded_graphics::mono_font::MonoTextStyle; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::text::Text; +use mipidsi::models::ST7789; +use mipidsi::options::{Orientation, Rotation}; +use mipidsi::Builder; +use {defmt_rtt as _, panic_probe as _}; + +use crate::touch::Touch; + +const DISPLAY_FREQ: u32 = 64_000_000; +const TOUCH_FREQ: u32 = 200_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let bl = p.PIN_13; + let rst = p.PIN_15; + let display_cs = p.PIN_9; + let dcx = p.PIN_8; + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + //let touch_irq = p.PIN_17; + + // create SPI + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + let mut touch_config = spi::Config::default(); + touch_config.frequency = TOUCH_FREQ; + touch_config.phase = spi::Phase::CaptureOnSecondTransition; + touch_config.polarity = spi::Polarity::IdleHigh; + + let spi: Spi<'_, _, Blocking> = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let touch_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(touch_cs, Level::High), touch_config); + + let mut touch = Touch::new(touch_spi); + + let dcx = Output::new(dcx, Level::Low); + let rst = Output::new(rst, Level::Low); + // dcx: 0 = command, 1 = data + + // Enable LCD backlight + let _bl = Output::new(bl, Level::High); + + // display interface abstraction from SPI and DC + let di = SPIInterface::new(display_spi, dcx); + + // Define the display from the display interface and initialize it + let mut display = Builder::new(ST7789, di) + .display_size(240, 320) + .reset_pin(rst) + .orientation(Orientation::new().rotate(Rotation::Deg90)) + .init(&mut Delay) + .unwrap(); + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), 86); + let ferris = Image::new(&raw_image_data, Point::new(34, 68)); + + // Display the image + ferris.draw(&mut display).unwrap(); + + let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN); + Text::new( + "Hello embedded_graphics \n + embassy + RP235x!", + Point::new(20, 200), + style, + ) + .draw(&mut display) + .unwrap(); + + loop { + if let Some((x, y)) = touch.read() { + let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLUE).build(); + + Rectangle::new(Point::new(x - 1, y - 1), Size::new(3, 3)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + } + } +} + +/// Driver for the XPT2046 resistive touchscreen sensor +mod touch { + use embedded_hal_1::spi::{Operation, SpiDevice}; + + struct Calibration { + x1: i32, + x2: i32, + y1: i32, + y2: i32, + sx: i32, + sy: i32, + } + + const CALIBRATION: Calibration = Calibration { + x1: 3880, + x2: 340, + y1: 262, + y2: 3850, + sx: 320, + sy: 240, + }; + + pub struct Touch { + spi: SPI, + } + + impl Touch + where + SPI: SpiDevice, + { + pub fn new(spi: SPI) -> Self { + Self { spi } + } + + pub fn read(&mut self) -> Option<(i32, i32)> { + let mut x = [0; 2]; + let mut y = [0; 2]; + self.spi + .transaction(&mut [ + Operation::Write(&[0x90]), + Operation::Read(&mut x), + Operation::Write(&[0xd0]), + Operation::Read(&mut y), + ]) + .unwrap(); + + let x = (u16::from_be_bytes(x) >> 3) as i32; + let y = (u16::from_be_bytes(y) >> 3) as i32; + + let cal = &CALIBRATION; + + let x = ((x - cal.x1) * cal.sx / (cal.x2 - cal.x1)).clamp(0, cal.sx); + let y = ((y - cal.y1) * cal.sy / (cal.y2 - cal.y1)).clamp(0, cal.sy); + if x == 0 && y == 0 { + None + } else { + Some((x, y)) + } + } + } +} diff --git a/examples/rp23/src/bin/spi_sdmmc.rs b/examples/rp235x/src/bin/spi_sdmmc.rs similarity index 79% rename from examples/rp23/src/bin/spi_sdmmc.rs rename to examples/rp235x/src/bin/spi_sdmmc.rs index d7af77a30..e14a62c31 100644 --- a/examples/rp23/src/bin/spi_sdmmc.rs +++ b/examples/rp235x/src/bin/spi_sdmmc.rs @@ -1,4 +1,4 @@ -//! This example shows how to use `embedded-sdmmc` with the RP2040 chip, over SPI. +//! This example shows how to use `embedded-sdmmc` with the RP235x chip, over SPI. //! //! The example will attempt to read a file `MY_FILE.TXT` from the root directory //! of the SD card and print its contents. @@ -9,7 +9,6 @@ use defmt::*; use embassy_embedded_hal::SetConfig; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::spi::Spi; use embassy_rp::{gpio, spi}; use embedded_hal_bus::spi::ExclusiveDevice; @@ -17,20 +16,6 @@ use embedded_sdmmc::sdcard::{DummyCsPin, SdCard}; use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - struct DummyTimesource(); impl embedded_sdmmc::TimeSource for DummyTimesource { @@ -48,7 +33,6 @@ impl embedded_sdmmc::TimeSource for DummyTimesource { #[embassy_executor::main] async fn main(_spawner: Spawner) { - embassy_rp::pac::SIO.spinlock(31).write_value(1); let p = embassy_rp::init(Default::default()); // SPI clock needs to be running at <= 400kHz during initialization @@ -66,7 +50,7 @@ async fn main(_spawner: Spawner) { // Now that the card is initialized, the SPI clock can go faster let mut config = spi::Config::default(); config.frequency = 16_000_000; - sdcard.spi(|dev| dev.bus_mut().set_config(&config)).ok(); + sdcard.spi(|dev| SetConfig::set_config(dev.bus_mut(), &config)).ok(); // Now let's look for volumes (also known as partitions) on our block device. // To do this we need a Volume Manager. It will take ownership of the block device. diff --git a/examples/rp235x/src/bin/trng.rs b/examples/rp235x/src/bin/trng.rs new file mode 100644 index 000000000..100d6b104 --- /dev/null +++ b/examples/rp235x/src/bin/trng.rs @@ -0,0 +1,48 @@ +//! This example shows TRNG usage + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::TRNG; +use embassy_rp::trng::Trng; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TRNG_IRQ => embassy_rp::trng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_rp::init(Default::default()); + + // Initialize the TRNG with default configuration + let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); + // A buffer to collect random bytes in. + let mut randomness = [0u8; 58]; + + let mut led = Output::new(peripherals.PIN_25, Level::Low); + + loop { + trng.fill_bytes(&mut randomness).await; + info!("Random bytes async {}", &randomness); + trng.blocking_fill_bytes(&mut randomness); + info!("Random bytes blocking {}", &randomness); + let random_u32 = trng.blocking_next_u32(); + let random_u64 = trng.blocking_next_u64(); + info!("Random u32 {} u64 {}", random_u32, random_u64); + // Random number of blinks between 0 and 31 + let blinks = random_u32 % 32; + for _ in 0..blinks { + led.set_high(); + Timer::after_millis(20).await; + led.set_low(); + Timer::after_millis(20).await; + } + Timer::after_millis(1000).await; + } +} diff --git a/examples/rp23/src/bin/uart.rs b/examples/rp235x/src/bin/uart.rs similarity index 59% rename from examples/rp23/src/bin/uart.rs rename to examples/rp235x/src/bin/uart.rs index ae00f36dc..ed912b959 100644 --- a/examples/rp23/src/bin/uart.rs +++ b/examples/rp235x/src/bin/uart.rs @@ -1,4 +1,4 @@ -//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP235x chip. //! //! No specific hardware is specified in this example. Only output on pin 0 is tested. //! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used @@ -8,24 +8,9 @@ #![no_main] use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::uart; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/uart_buffered_split.rs b/examples/rp235x/src/bin/uart_buffered_split.rs similarity index 72% rename from examples/rp23/src/bin/uart_buffered_split.rs rename to examples/rp235x/src/bin/uart_buffered_split.rs index 2b14520d5..7cad09f9b 100644 --- a/examples/rp23/src/bin/uart_buffered_split.rs +++ b/examples/rp235x/src/bin/uart_buffered_split.rs @@ -1,4 +1,4 @@ -//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP235x chip. //! //! No specific hardware is specified in this example. If you connect pin 0 and 1 you should get the same data back. //! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used @@ -10,7 +10,6 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::peripherals::UART0; use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config}; use embassy_time::Timer; @@ -18,20 +17,6 @@ use embedded_io_async::{Read, Write}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; }); @@ -45,7 +30,7 @@ async fn main(spawner: Spawner) { let tx_buf = &mut TX_BUF.init([0; 16])[..]; static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); let rx_buf = &mut RX_BUF.init([0; 16])[..]; - let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let uart = BufferedUart::new(uart, tx_pin, rx_pin, Irqs, tx_buf, rx_buf, Config::default()); let (mut tx, rx) = uart.split(); unwrap!(spawner.spawn(reader(rx))); @@ -63,7 +48,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: BufferedUartRx<'static, UART0>) { +async fn reader(mut rx: BufferedUartRx) { info!("Reading..."); loop { let mut buf = [0; 31]; diff --git a/examples/rp23/src/bin/uart_r503.rs b/examples/rp235x/src/bin/uart_r503.rs similarity index 92% rename from examples/rp23/src/bin/uart_r503.rs rename to examples/rp235x/src/bin/uart_r503.rs index 39a17d305..085be280b 100644 --- a/examples/rp23/src/bin/uart_r503.rs +++ b/examples/rp235x/src/bin/uart_r503.rs @@ -4,27 +4,12 @@ use defmt::{debug, error, info}; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::peripherals::UART0; use embassy_rp::uart::{Config, DataBits, InterruptHandler as UARTInterruptHandler, Parity, StopBits, Uart}; use embassy_time::{with_timeout, Duration, Timer}; use heapless::Vec; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(pub struct Irqs { UART0_IRQ => UARTInterruptHandler; }); diff --git a/examples/rp23/src/bin/uart_unidir.rs b/examples/rp235x/src/bin/uart_unidir.rs similarity index 70% rename from examples/rp23/src/bin/uart_unidir.rs rename to examples/rp235x/src/bin/uart_unidir.rs index 38210a8d0..45c9c8407 100644 --- a/examples/rp23/src/bin/uart_unidir.rs +++ b/examples/rp235x/src/bin/uart_unidir.rs @@ -1,4 +1,4 @@ -//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP235x chip. //! //! Test TX-only and RX-only on two different UARTs. You need to connect GPIO0 to GPIO5 for //! this to work @@ -11,26 +11,11 @@ use defmt::*; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::peripherals::UART1; use embassy_rp::uart::{Async, Config, InterruptHandler, UartRx, UartTx}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { UART1_IRQ => InterruptHandler; }); @@ -54,7 +39,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: UartRx<'static, UART1, Async>) { +async fn reader(mut rx: UartRx<'static, Async>) { info!("Reading..."); loop { // read a total of 4 transmissions (32 / 8) and then print the result diff --git a/examples/rp235x/src/bin/usb_hid_keyboard.rs b/examples/rp235x/src/bin/usb_hid_keyboard.rs new file mode 100644 index 000000000..6f496e23a --- /dev/null +++ b/examples/rp235x/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,188 @@ +#![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::gpio::{Input, Pull}; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler}; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State as HidState}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // Create the driver, from the HAL. + let driver = UsbDriver::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 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 mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = HidState::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + 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 { + loop { + info!("Waiting for HIGH on pin 16"); + signal_pin.wait_for_high().await; + info!("HIGH DETECTED"); + // Create a report with the A key pressed. (no shift modifier) + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + signal_pin.wait_for_low().await; + info!("LOW DETECTED"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + reader.run(false, &mut 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(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} diff --git a/examples/rp23/src/bin/usb_webusb.rs b/examples/rp235x/src/bin/usb_webusb.rs similarity index 87% rename from examples/rp23/src/bin/usb_webusb.rs rename to examples/rp235x/src/bin/usb_webusb.rs index f4ecde30e..75d28c853 100644 --- a/examples/rp23/src/bin/usb_webusb.rs +++ b/examples/rp235x/src/bin/usb_webusb.rs @@ -1,4 +1,4 @@ -//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! This example shows how to use USB (Universal Serial Bus) in the RP235x chip. //! //! This creates a WebUSB capable device that echoes data back to the host. //! @@ -21,7 +21,6 @@ use defmt::info; use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler}; use embassy_usb::class::web_usb::{Config as WebUsbConfig, State, Url, WebUsb}; @@ -30,20 +29,6 @@ use embassy_usb::msos::{self, windows_version}; use embassy_usb::{Builder, Config}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - bind_interrupts!(struct Irqs { USBCTRL_IRQ => InterruptHandler; }); @@ -66,12 +51,6 @@ async fn main(_spawner: Spawner) { 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 = 0xff; - config.device_sub_class = 0x00; - config.device_protocol = 0x00; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/rp23/src/bin/watchdog.rs b/examples/rp235x/src/bin/watchdog.rs similarity index 72% rename from examples/rp23/src/bin/watchdog.rs rename to examples/rp235x/src/bin/watchdog.rs index 3ac457219..a54ec493a 100644 --- a/examples/rp23/src/bin/watchdog.rs +++ b/examples/rp235x/src/bin/watchdog.rs @@ -1,4 +1,4 @@ -//! This example shows how to use Watchdog in the RP2040 chip. +//! This example shows how to use Watchdog in the RP235x chip. //! //! It does not work with the RP Pico W board. See wifi_blinky.rs or connect external LED and resistor. @@ -7,27 +7,12 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_rp::block::ImageDef; use embassy_rp::gpio; use embassy_rp::watchdog::*; use embassy_time::{Duration, Timer}; use gpio::{Level, Output}; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); diff --git a/examples/rp23/src/bin/zerocopy.rs b/examples/rp235x/src/bin/zerocopy.rs similarity index 79% rename from examples/rp23/src/bin/zerocopy.rs rename to examples/rp235x/src/bin/zerocopy.rs index d04e1bf2a..086c86cac 100644 --- a/examples/rp23/src/bin/zerocopy.rs +++ b/examples/rp235x/src/bin/zerocopy.rs @@ -1,6 +1,6 @@ //! This example shows how to use `zerocopy_channel` from `embassy_sync` for //! sending large values between two tasks without copying. -//! The example also shows how to use the RP2040 ADC with DMA. +//! The example also shows how to use the RP235x ADC with DMA. #![no_std] #![no_main] @@ -9,30 +9,15 @@ use core::sync::atomic::{AtomicU16, Ordering}; use defmt::*; use embassy_executor::Spawner; use embassy_rp::adc::{self, Adc, Async, Config, InterruptHandler}; -use embassy_rp::bind_interrupts; -use embassy_rp::block::ImageDef; use embassy_rp::gpio::Pull; use embassy_rp::peripherals::DMA_CH0; +use embassy_rp::{bind_interrupts, Peri}; use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::zerocopy_channel::{Channel, Receiver, Sender}; use embassy_time::{Duration, Ticker, Timer}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".start_block"] -#[used] -pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); - -// Program metadata for `picotool info` -#[link_section = ".bi_entries"] -#[used] -pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ - embassy_rp::binary_info_rp_cargo_bin_name!(), - embassy_rp::binary_info_rp_cargo_version!(), - embassy_rp::binary_info_rp_program_description!(c"Blinky"), - embassy_rp::binary_info_rp_program_build_attribute!(), -]; - type SampleBuffer = [u16; 512]; bind_interrupts!(struct Irqs { @@ -46,7 +31,7 @@ static MAX: AtomicU16 = AtomicU16::new(0); struct AdcParts { adc: Adc<'static, Async>, pin: adc::Channel<'static>, - dma: DMA_CH0, + dma: Peri<'static, DMA_CH0>, } #[embassy_executor::main] @@ -85,7 +70,10 @@ async fn producer(mut sender: Sender<'static, NoopRawMutex, SampleBuffer>, mut a let buf = sender.send().await; // Fill it with data - adc.adc.read_many(&mut adc.pin, buf, 1, &mut adc.dma).await.unwrap(); + adc.adc + .read_many(&mut adc.pin, buf, 1, adc.dma.reborrow()) + .await + .unwrap(); // Notify the channel that the buffer is now ready to be received sender.send_done(); diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 87491b1d2..63740963d 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -5,12 +5,12 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "std", ] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-std", "executor-thread", "log"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["log", "std", ] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features=[ "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } -embassy-net-ppp = { version = "0.1.0", path = "../../embassy-net-ppp", features = ["log"]} +embassy-net-ppp = { version = "0.2.0", path = "../../embassy-net-ppp", features = ["log"]} embedded-io-async = { version = "0.6.1" } embedded-io-adapters = { version = "0.6.1", features = ["futures-03"] } critical-section = { version = "1.1", features = ["std"] } @@ -21,7 +21,7 @@ futures = { version = "0.3.17" } log = "0.4.14" nix = "0.26.2" clap = { version = "3.0.0-beta.5", features = ["derive"] } -rand_core = { version = "0.6.3", features = ["std"] } +rand_core = { version = "0.9.1", features = ["std", "os_rng"] } heapless = { version = "0.8", default-features = false } static_cell = "2" diff --git a/examples/std/README.md b/examples/std/README.md index e3a59d6ea..ac2c2a1a6 100644 --- a/examples/std/README.md +++ b/examples/std/README.md @@ -1,23 +1,128 @@ ## Running the `embassy-net` examples -First, create the tap0 interface. You only need to do this once. +To run `net`, `tcp_accept`, `net_udp` and `net_dns` examples you will need a tap interface. Before running these examples, create the tap99 interface. (The number was chosen to +hopefully not collide with anything.) You only need to do this once every time you reboot your computer. ```sh -sudo ip tuntap add name tap0 mode tap user $USER -sudo ip link set tap0 up -sudo ip addr add 192.168.69.100/24 dev tap0 -sudo ip -6 addr add fe80::100/64 dev tap0 -sudo ip -6 addr add fdaa::100/64 dev tap0 -sudo ip -6 route add fe80::/64 dev tap0 -sudo ip -6 route add fdaa::/64 dev tap0 +cd $EMBASSY_ROOT/examples/std/ +sudo sh tap.sh ``` -Second, have something listening there. For example `nc -lp 8000` +The example `net_ppp` requires different steps that are detailed in its section. + +### `net` example + +For this example, you need to have something listening in the correct port. 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 +cargo run --bin net -- --tap tap99 --static-ip +``` +### `tcp_accept` example + +This example listen for a tcp connection. + +First run the example located in the `examples` folder: + +```sh +cd $EMBASSY_ROOT/examples/std/ +cargo run --bin tcp_accept -- --tap tap99 --static-ip +``` + +Then open a connection to the port. For example `nc 192.168.69.2 9999`. + +### `net_udp` example + +This example listen for a udp connection. + +First run the example located in the `examples` folder: + +```sh +cd $EMBASSY_ROOT/examples/std/ +cargo run --bin net_udp -- --tap tap99 --static-ip +``` + +Then open a connection to the port. For example `nc -u 192.168.69.2 9400`. + +### `net_dns` example + +This example queries a `DNS` for the IP address of `www.example.com`. + +In order to achieve this, the `tap99` interface requires configuring tap99 as a gateway device temporarily. + +For example, in Ubuntu you can do this by: + +1. Identifying your default route device. In the next example `eth0` + +```sh +ip r | grep "default" +default via 192.168.2.1 dev eth0 proto kernel metric 35 +``` + +2. Enabling temporarily IP Forwarding: + +```sh +sudo sysctl -w net.ipv4.ip_forward=1 +``` + +3. Configuring NAT to mascarade traffic from `tap99` to `eth0` + +```sh +sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +sudo iptables -A FORWARD -i tap99 -j ACCEPT +sudo iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT +``` + +4. Then you can run the example located in the `examples` folder: + +```sh +cd $EMBASSY_ROOT/examples/std/ +cargo run --bin net_dns -- --tap tap99 --static-ip +``` + +### `net_ppp` example + +This example establish a Point-to-Point Protocol (PPP) connection that can be used, for example, for connecting to internet through a 4G modem via a serial channel. + +The example creates a PPP bridge over a virtual serial channel between `pty1` and `pty2` for the example code and a PPP server running on the same computer. + +To run this example you will need: +- ppp (pppd server) +- socat (socket CAT) + +To run the examples you may follow the next steps: + +1. Save the PPP server configuration: +```sh +sudo sh -c 'echo "myuser $(hostname) mypass 192.168.7.10" >> /etc/ppp/pap-secrets' +``` + +2. Create a files `pty1` and `pty2` and link them +```sh +cd $EMBASSY_ROOT/examples/std/ +socat -v -x PTY,link=pty1,rawer PTY,link=pty2,rawer +``` + +3. open a second terminal and start the PPP server: +```sh +cd $EMBASSY_ROOT/examples/std/ +sudo pppd $PWD/pty1 115200 192.168.7.1: ms-dns 8.8.4.4 ms-dns 8.8.8.8 nodetach debug local persist silent +``` + +4. Open a third terminal and run the example +```sh +cd $EMBASSY_ROOT/examples/std/ +RUST_LOG=trace cargo run --bin net_ppp -- --device pty2 +``` +5. Observe the output in the second and third terminal +6. Open one last terminal to interact with `net_ppp` example through the PPP connection +```sh +# ping the net_ppp client +ping 192.168.7.10 +# open an tcp connection +nc 192.168.7.10 1234 +# Type anything and observe the output in the different terminals ``` diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 310e7264d..232cf494b 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -1,13 +1,15 @@ +use core::fmt::Write as _; + use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use embassy_time::Duration; use embedded_io_async::Write; use heapless::Vec; use log::*; -use rand_core::{OsRng, RngCore}; +use rand_core::{OsRng, TryRngCore}; use static_cell::StaticCell; #[derive(Parser)] @@ -22,8 +24,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -46,16 +48,15 @@ async fn main_task(spawner: Spawner) { // Generate random seed let mut seed = [0; 8]; - OsRng.fill_bytes(&mut seed); + OsRng.try_fill_bytes(&mut seed).unwrap(); let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_buffer = [0; 4096]; @@ -72,8 +73,10 @@ async fn main_task(spawner: Spawner) { return; } info!("connected!"); - loop { - let r = socket.write_all(b"Hello!\n").await; + for i in 0.. { + let mut buf = heapless::String::<100>::new(); + write!(buf, "Hello! ({})\r\n", i).unwrap(); + let r = socket.write_all(buf.as_bytes()).await; if let Err(e) = r { warn!("write error: {:?}", e); return; diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs index c9615ef35..cf90731dd 100644 --- a/examples/std/src/bin/net_dns.rs +++ b/examples/std/src/bin/net_dns.rs @@ -1,11 +1,11 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::dns::DnsQueryType; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use heapless::Vec; use log::*; -use rand_core::{OsRng, RngCore}; +use rand_core::{OsRng, TryRngCore}; use static_cell::StaticCell; #[derive(Parser)] @@ -20,8 +20,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -45,16 +45,15 @@ async fn main_task(spawner: Spawner) { // Generate random seed let mut seed = [0; 8]; - OsRng.fill_bytes(&mut seed); + OsRng.try_fill_bytes(&mut seed).unwrap(); let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack: &Stack<_> = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); let host = "example.com"; info!("querying host {:?}...", host); diff --git a/examples/std/src/bin/net_ppp.rs b/examples/std/src/bin/net_ppp.rs index c5c27c4a3..ac3aea6ff 100644 --- a/examples/std/src/bin/net_ppp.rs +++ b/examples/std/src/bin/net_ppp.rs @@ -16,14 +16,14 @@ use async_io::Async; use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, ConfigV4, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, ConfigV4, Ipv4Cidr, Stack, StackResources}; use embassy_net_ppp::Runner; use embedded_io_async::Write; use futures::io::BufReader; use heapless::Vec; use log::*; use nix::sys::termios; -use rand_core::{OsRng, RngCore}; +use rand_core::{OsRng, TryRngCore}; use static_cell::StaticCell; use crate::serial_port::SerialPort; @@ -37,19 +37,15 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_ppp::Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::task] -async fn ppp_task( - stack: &'static Stack>, - mut runner: Runner<'static>, - port: SerialPort, -) -> ! { +async fn ppp_task(stack: Stack<'static>, mut runner: Runner<'static>, port: SerialPort) -> ! { let port = Async::new(port).unwrap(); let port = BufReader::new(port); - let port = adapter::FromFutures::new(port); + let port = embedded_io_adapters::futures_03::FromFutures::new(port); let config = embassy_net_ppp::Config { username: b"myuser", @@ -64,10 +60,10 @@ async fn ppp_task( }; let mut dns_servers = Vec::new(); for s in ipv4.dns_servers.iter().flatten() { - let _ = dns_servers.push(Ipv4Address::from_bytes(&s.0)); + let _ = dns_servers.push(*s); } let config = ConfigV4::Static(embassy_net::StaticConfigV4 { - address: Ipv4Cidr::new(Ipv4Address::from_bytes(&addr.0), 0), + address: Ipv4Cidr::new(addr, 0), gateway: None, dns_servers, }); @@ -93,21 +89,20 @@ async fn main_task(spawner: Spawner) { // Generate random seed let mut seed = [0; 8]; - OsRng.fill_bytes(&mut seed); + OsRng.try_fill_bytes(&mut seed).unwrap(); let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, net_runner) = embassy_net::new( device, Config::default(), // don't configure IP yet RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(net_runner)).unwrap(); spawner.spawn(ppp_task(stack, runner, port)).unwrap(); // Then we can use it! @@ -168,53 +163,3 @@ fn main() { spawner.spawn(main_task(spawner)).unwrap(); }); } - -mod adapter { - use core::future::poll_fn; - use core::pin::Pin; - - use futures::AsyncBufReadExt; - - /// Adapter from `futures::io` traits. - #[derive(Clone)] - pub struct FromFutures { - inner: T, - } - - impl FromFutures { - /// Create a new adapter. - pub fn new(inner: T) -> Self { - Self { inner } - } - } - - impl embedded_io_async::ErrorType for FromFutures { - type Error = std::io::Error; - } - - impl embedded_io_async::Read for FromFutures { - async fn read(&mut self, buf: &mut [u8]) -> Result { - poll_fn(|cx| Pin::new(&mut self.inner).poll_read(cx, buf)).await - } - } - - impl embedded_io_async::BufRead for FromFutures { - async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - self.inner.fill_buf().await - } - - fn consume(&mut self, amt: usize) { - Pin::new(&mut self.inner).consume(amt) - } - } - - impl embedded_io_async::Write for FromFutures { - async fn write(&mut self, buf: &[u8]) -> Result { - poll_fn(|cx| Pin::new(&mut self.inner).poll_write(cx, buf)).await - } - - async fn flush(&mut self) -> Result<(), Self::Error> { - poll_fn(|cx| Pin::new(&mut self.inner).poll_flush(cx)).await - } - } -} diff --git a/examples/std/src/bin/net_udp.rs b/examples/std/src/bin/net_udp.rs index b2ba4915a..53632a5b4 100644 --- a/examples/std/src/bin/net_udp.rs +++ b/examples/std/src/bin/net_udp.rs @@ -1,11 +1,11 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::udp::{PacketMetadata, UdpSocket}; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use heapless::Vec; use log::*; -use rand_core::{OsRng, RngCore}; +use rand_core::{OsRng, TryRngCore}; use static_cell::StaticCell; #[derive(Parser)] @@ -20,8 +20,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -44,16 +44,15 @@ async fn main_task(spawner: Spawner) { // Generate random seed let mut seed = [0; 8]; - OsRng.fill_bytes(&mut seed); + OsRng.try_fill_bytes(&mut seed).unwrap(); let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_meta = [PacketMetadata::EMPTY; 16]; diff --git a/examples/std/src/bin/tcp_accept.rs b/examples/std/src/bin/tcp_accept.rs index 39b29a449..961c20e2d 100644 --- a/examples/std/src/bin/tcp_accept.rs +++ b/examples/std/src/bin/tcp_accept.rs @@ -1,15 +1,13 @@ -use core::fmt::Write as _; - use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use embassy_time::{Duration, Timer}; use embedded_io_async::Write as _; use heapless::Vec; use log::*; -use rand_core::{OsRng, RngCore}; +use rand_core::{OsRng, TryRngCore}; use static_cell::StaticCell; #[derive(Parser)] @@ -24,18 +22,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await -} - -#[derive(Default)] -struct StrWrite(pub heapless::Vec); - -impl core::fmt::Write for StrWrite { - fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { - self.0.extend_from_slice(s.as_bytes()).unwrap(); - Ok(()) - } +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -58,16 +46,15 @@ async fn main_task(spawner: Spawner) { // Generate random seed let mut seed = [0; 8]; - OsRng.fill_bytes(&mut seed); + OsRng.try_fill_bytes(&mut seed).unwrap(); let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_buffer = [0; 4096]; @@ -86,9 +73,8 @@ async fn main_task(spawner: Spawner) { // Write some quick output for i in 1..=5 { - let mut w = StrWrite::default(); - write!(w, "{}! ", i).unwrap(); - let r = socket.write_all(&w.0).await; + let s = format!("{}! ", i); + let r = socket.write_all(s.as_bytes()).await; if let Err(e) = r { warn!("write error: {:?}", e); return; diff --git a/examples/std/tap.sh b/examples/std/tap.sh new file mode 100644 index 000000000..fb89d2381 --- /dev/null +++ b/examples/std/tap.sh @@ -0,0 +1,7 @@ +ip tuntap add name tap99 mode tap user $SUDO_USER +ip link set tap99 up +ip addr add 192.168.69.100/24 dev tap99 +ip -6 addr add fe80::100/64 dev tap99 +ip -6 addr add fdaa::100/64 dev tap99 +ip -6 route add fe80::/64 dev tap99 +ip -6 route add fdaa::/64 dev tap99 diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml index 9102467eb..4cf07cef4 100644 --- a/examples/stm32c0/Cargo.toml +++ b/examples/stm32c0/Cargo.toml @@ -6,18 +6,18 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32c031c6 to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } [profile.release] diff --git a/examples/stm32c0/src/bin/adc.rs b/examples/stm32c0/src/bin/adc.rs new file mode 100644 index 000000000..1f54b0b18 --- /dev/null +++ b/examples/stm32c0/src/bin/adc.rs @@ -0,0 +1,58 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::vals::Scandir; +use embassy_stm32::adc::{Adc, AdcChannel, AnyAdcChannel, Resolution, SampleTime}; +use embassy_stm32::peripherals::ADC1; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Default::default(); + let p = embassy_stm32::init(config); + + info!("ADC STM32C0 example."); + + // We need to set certain sample time to be able to read temp sensor. + let mut adc = Adc::new(p.ADC1, SampleTime::CYCLES12_5, Resolution::BITS12); + let mut temp = adc.enable_temperature().degrade_adc(); + let mut vref = adc.enable_vrefint().degrade_adc(); + let mut pin0 = p.PA0.degrade_adc(); + + let mut dma = p.DMA1_CH1; + let mut read_buffer: [u16; 3] = [0; 3]; + + loop { + info!("============================"); + let blocking_temp = adc.blocking_read(&mut temp); + let blocking_vref = adc.blocking_read(&mut vref); + let blocing_pin0 = adc.blocking_read(&mut pin0); + info!( + "Blocking ADC read: vref = {}, temp = {}, pin0 = {}.", + blocking_vref, blocking_temp, blocing_pin0 + ); + + let channels_seqence: [&mut AnyAdcChannel; 3] = [&mut vref, &mut temp, &mut pin0]; + adc.read(dma.reborrow(), channels_seqence.into_iter(), &mut read_buffer) + .await; + // Values are ordered according to hardware ADC channel number! + info!( + "DMA ADC read in set: vref = {}, temp = {}, pin0 = {}.", + read_buffer[0], read_buffer[1], read_buffer[2] + ); + + let hw_channel_selection: u32 = + (1 << temp.get_hw_channel()) + (1 << vref.get_hw_channel()) + (1 << pin0.get_hw_channel()); + adc.read_in_hw_order(dma.reborrow(), hw_channel_selection, Scandir::UP, &mut read_buffer) + .await; + info!( + "DMA ADC read in hardware order: vref = {}, temp = {}, pin0 = {}.", + read_buffer[2], read_buffer[1], read_buffer[0] + ); + + Timer::after_millis(2000).await; + } +} diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 724efdaff..400e6b94c 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -6,15 +6,15 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32f091rc to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "memory-x", "stm32f091rc", "time-driver-any", "exti", "unstable-pac"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "memory-x", "stm32f091rc", "time-driver-tim2", "exti", "unstable-pac"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -defmt = "0.3" -defmt-rtt = "0.4" -panic-probe = { version = "0.3", features = ["print-defmt"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } static_cell = "2" portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32f0/src/bin/button_controlled_blink.rs b/examples/stm32f0/src/bin/button_controlled_blink.rs index 4465483d9..744df3e3b 100644 --- a/examples/stm32f0/src/bin/button_controlled_blink.rs +++ b/examples/stm32f0/src/bin/button_controlled_blink.rs @@ -8,14 +8,15 @@ use core::sync::atomic::{AtomicU32, Ordering}; use defmt::info; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{AnyPin, Level, Output, Pin, Pull, Speed}; +use embassy_stm32::gpio::{AnyPin, Level, Output, Pull, Speed}; +use embassy_stm32::Peri; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; static BLINK_MS: AtomicU32 = AtomicU32::new(0); #[embassy_executor::task] -async fn led_task(led: AnyPin) { +async fn led_task(led: Peri<'static, AnyPin>) { // Configure the LED pin as a push pull output and obtain handler. // On the Nucleo F091RC there's an on-board LED connected to pin PA5. let mut led = Output::new(led, Level::Low, Speed::Low); @@ -45,7 +46,7 @@ async fn main(spawner: Spawner) { BLINK_MS.store(del_var, Ordering::Relaxed); // Spawn LED blinking task - spawner.spawn(led_task(p.PA5.degrade())).unwrap(); + spawner.spawn(led_task(p.PA5.into())).unwrap(); loop { // Check if button got pressed diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 0084651a3..261733305 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -6,20 +6,20 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32f103c8 to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any" ] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any" ] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } nb = "1.0.0" static_cell = "2.0.0" diff --git a/examples/stm32f1/src/bin/input_capture.rs b/examples/stm32f1/src/bin/input_capture.rs index 5e2dab9e6..6fe8e0b50 100644 --- a/examples/stm32f1/src/bin/input_capture.rs +++ b/examples/stm32f1/src/bin/input_capture.rs @@ -7,14 +7,14 @@ use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_stm32::time::khz; use embassy_stm32::timer::input_capture::{CapturePin, InputCapture}; use embassy_stm32::timer::{self, Channel}; -use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::{bind_interrupts, peripherals, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; /// Connect PA2 and PC13 with a 1k Ohm resistor #[embassy_executor::task] -async fn blinky(led: peripherals::PC13) { +async fn blinky(led: Peri<'static, peripherals::PC13>) { let mut led = Output::new(led, Level::High, Speed::Low); loop { diff --git a/examples/stm32f1/src/bin/pwm_input.rs b/examples/stm32f1/src/bin/pwm_input.rs index f74853d4e..afbef3edb 100644 --- a/examples/stm32f1/src/bin/pwm_input.rs +++ b/examples/stm32f1/src/bin/pwm_input.rs @@ -6,14 +6,14 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_stm32::time::khz; use embassy_stm32::timer::pwm_input::PwmInput; -use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_stm32::{bind_interrupts, peripherals, timer, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; /// Connect PA0 and PC13 with a 1k Ohm resistor #[embassy_executor::task] -async fn blinky(led: peripherals::PC13) { +async fn blinky(led: Peri<'static, peripherals::PC13>) { let mut led = Output::new(led, Level::High, Speed::Low); loop { diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs index ee99acf41..77ec307b9 100644 --- a/examples/stm32f1/src/bin/usb_serial.rs +++ b/examples/stm32f1/src/bin/usb_serial.rs @@ -47,7 +47,7 @@ async fn main(_spawner: Spawner) { // Pull the D+ pin down to send a RESET condition to the USB bus. // This forced reset is needed only for development, without it host // will not reset your device when you upload new firmware. - let _dp = Output::new(&mut p.PA12, Level::Low, Speed::Low); + let _dp = Output::new(p.PA12.reborrow(), Level::Low, Speed::Low); Timer::after_millis(10).await; } diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index 60eb0eb93..905cffff0 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -6,18 +6,18 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32f207zg to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } nb = "1.0.0" diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index 7fda410d9..f675b0be1 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -6,20 +6,20 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32f303ze to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-tim2", "exti"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } nb = "1.0.0" embedded-storage = "0.3.1" diff --git a/examples/stm32f3/README.md b/examples/stm32f3/README.md new file mode 100644 index 000000000..0a85c4858 --- /dev/null +++ b/examples/stm32f3/README.md @@ -0,0 +1,24 @@ +# Examples for STM32F3 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for F303ZE it should be `probe-rs run --chip STM32F303ZETx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for F303ZE it should be `stm32f303ze`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32f3/src/bin/blocking-tsc.rs b/examples/stm32f3/src/bin/blocking-tsc.rs deleted file mode 100644 index 5c8dac94f..000000000 --- a/examples/stm32f3/src/bin/blocking-tsc.rs +++ /dev/null @@ -1,98 +0,0 @@ -// Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected. -// -// Suggested physical setup on STM32F303ZE Nucleo board: -// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. -// - Connect one end of a 1K resistor to pin A1 and leave the other end loose. -// The loose end will act as touch sensor which will register your touch. -// -// Troubleshooting the setup: -// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, -// now the led should light up. Next try using a different value for the sampling capacitor. -// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. -// -// All configuration values and sampling capacitor value have been determined experimentally. -// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values. -// -#![no_std] -#![no_main] - -use defmt::*; -use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::tsc::{self, *}; -use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; - -/// This example is written for the nucleo-stm32f303ze, with a stm32f303ze chip. -/// -/// Make sure you check/update the following (whether you use the F303ZE or another board): -/// -/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32F303ZETx`chip name. -/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for F303ZE it should be `stm32f303ze`. -/// * [ ] If your board has a special clock or power configuration, make sure that it is -/// set up appropriately. -/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals -/// to match your schematic -/// -/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: -/// -/// * Which example you are trying to run -/// * Which chip and board you are using -/// -/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org -#[embassy_executor::main] -async fn main(_spawner: embassy_executor::Spawner) { - let device_config = embassy_stm32::Config::default(); - let context = embassy_stm32::init(device_config); - - let tsc_conf = Config { - ct_pulse_high_length: ChargeTransferPulseCycle::_8, - ct_pulse_low_length: ChargeTransferPulseCycle::_8, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(2).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_32, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: TscIOPin::Group1Io1.into(), - shield_ios: 0, // no shield - sampling_ios: TscIOPin::Group1Io2.into(), - }; - - let mut g1: PinGroup = PinGroup::new(); - g1.set_io1(context.PA0, PinType::Sample); - g1.set_io2(context.PA1, PinType::Channel); - - let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, Some(g1), None, None, None, None, None, tsc_conf); - - // LED2 on the STM32F303ZE nucleo-board - let mut led = Output::new(context.PB7, Level::High, Speed::Low); - - // smaller sample capacitor discharge faster and can be used with shorter delay. - let discharge_delay = 5; // ms - - // the interval at which the loop polls for new touch sensor values - let polling_interval = 100; // ms - - info!("polling for touch"); - loop { - touch_controller.start(); - touch_controller.poll_for_acquisition(); - touch_controller.discharge_io(true); - Timer::after_millis(discharge_delay).await; - - let grp1_status = touch_controller.group_get_status(Group::One); - match grp1_status { - GroupStatus::Complete => { - let group_one_val = touch_controller.group_get_value(Group::One); - info!("{}", group_one_val); - led.set_high(); - } - GroupStatus::Ongoing => led.set_low(), - } - - Timer::after_millis(polling_interval).await; - } -} diff --git a/examples/stm32f3/src/bin/tsc_blocking.rs b/examples/stm32f3/src/bin/tsc_blocking.rs new file mode 100644 index 000000000..2c33838e5 --- /dev/null +++ b/examples/stm32f3/src/bin/tsc_blocking.rs @@ -0,0 +1,138 @@ +// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32F303ZE Nucleo board: +// - Connect a 1000pF capacitor between pin PA10 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA9 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 4 of the TSC: +// - PA10 as the sampling capacitor, TSC group 4 IO2 (D68 on the STM32F303ZE nucleo-board) +// - PA9 as the channel pin, TSC group 4 IO1 (D69 on the STM32F303ZE nucleo-board) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 40). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32F303ZE board. Refer to the +// official relevant STM32 datasheets and user nucleo-board user manuals to find suitable +// alternative pins. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g: PinGroupWithRoles = PinGroupWithRoles::default(); + // D68 on the STM32F303ZE nucleo-board + g.set_io2::(context.PA10); + // D69 on the STM32F303ZE nucleo-board + let tsc_sensor = g.set_io1::(context.PA9); + + let pin_groups: PinGroups = PinGroups { + g4: Some(g.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + // LED2 on the STM32F303ZE nucleo-board + let mut led = Output::new(context.PB7, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: tsc::IOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/examples/stm32f3/src/bin/tsc_multipin.rs b/examples/stm32f3/src/bin/tsc_multipin.rs new file mode 100644 index 000000000..c524c3760 --- /dev/null +++ b/examples/stm32f3/src/bin/tsc_multipin.rs @@ -0,0 +1,204 @@ +// Example of TSC (Touch Sensing Controller) using multiple pins from the same tsc-group. +// +// What is special about using multiple TSC pins as sensor channels from the same TSC group, +// is that only one TSC pin for each TSC group can be acquired and read at the time. +// To control which channel pins are acquired and read, we must write a mask before initiating an +// acquisition. To help manage and abstract all this business away, we can organize our channel +// pins into acquisition banks. Each acquisition bank can contain exactly one channel pin per TSC +// group and it will contain the relevant mask. +// +// This example demonstrates how to: +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels_bank method to switch between sets of different channels (acquisition banks) +// 3. Read and interpret touch values from multiple channels in the same group +// +// Suggested physical setup on STM32F303ZE Nucleo board: +// - Connect a 1000pF capacitor between pin PA10 and GND. This is the sampling capacitor for TSC +// group 4. +// - Connect one end of a 1K resistor to pin PA9 and leave the other end loose. +// The loose end will act as a touch sensor. +// +// - Connect a 1000pF capacitor between pin PA7 and GND. This is the sampling capacitor for TSC +// group 2. +// - Connect one end of another 1K resistor to pin PA6 and leave the other end loose. +// The loose end will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PA5 and leave the other end loose. +// The loose end will act as a touch sensor. +// +// The example uses pins from two TSC groups. +// - PA10 as sampling capacitor, TSC group 4 IO2 +// - PA9 as channel, TSC group 4 IO1 +// - PA7 as sampling capacitor, TSC group 2 IO4 +// - PA6 as channel, TSC group 2 IO3 +// - PA5 as channel, TSC group 2 IO2 +// +// The pins have been chosen to make it easy to simply add capacitors directly onto the board and +// connect one leg to GND, and to easily add resistors to the board with no special connectors, +// breadboards, special wires or soldering required. All you need is the capacitors and resistors. +// +// The program reads the designated channel pins and adjusts the LED blinking +// pattern based on which sensor(s) are touched: +// - No touch: LED off +// - one sensor touched: Slow blinking +// - two sensors touched: Fast blinking +// - three sensors touched: LED constantly on +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 20). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards there will be overlapping concerns between some pins, for +// example UART connection for the programmer to the MCU and a TSC pin. No errors or warning will +// be emitted if you try to use such a pin for TSC, but you will get strange sensor readings. +// +// Note: Configuration values and sampling capacitor values have been determined experimentally. Optimal values may vary based on your specific hardware setup. Refer to the official STM32 datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 10; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Blocking>, + tsc_acquisition_bank: &AcquisitionBank, +) { + touch_controller.set_active_channels_bank(tsc_acquisition_bank); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + let discharge_delay = 5; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + // + let mut pin_group4: PinGroupWithRoles = PinGroupWithRoles::default(); + // D68 on the STM32F303ZE nucleo-board + pin_group4.set_io2::(context.PA10); + // D69 on the STM32F303ZE nucleo-board + let tsc_sensor0 = pin_group4.set_io1(context.PA9); + + let mut pin_group2: PinGroupWithRoles = PinGroupWithRoles::default(); + // D11 on the STM32F303ZE nucleo-board + pin_group2.set_io4::(context.PA7); + // D12 on the STM32F303ZE nucleo-board + let tsc_sensor1 = pin_group2.set_io3(context.PA6); + // D13 on the STM32F303ZE nucleo-board + let tsc_sensor2 = pin_group2.set_io2(context.PA5); + + let config = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g4: Some(pin_group4.pin_group), + g2: Some(pin_group2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, config).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 in this example belong to different TSC-groups, + // therefore we can acquire and read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g4_pin: Some(tsc_sensor0), + g2_pin: Some(tsc_sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. Therefore, we organize them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g2_pin: Some(tsc_sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + // LED2 on the STM32F303ZE nucleo-board + let mut led = Output::new(context.PB7, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1 = touch_controller.get_acquisition_bank_values(&bank1); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2 = touch_controller.get_acquisition_bank_values(&bank2); + + let mut touched_sensors_count = 0; + for reading in readings1.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + for reading in readings2.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/examples/stm32f334/Cargo.toml b/examples/stm32f334/Cargo.toml index 1cc0a97da..b47a81e1b 100644 --- a/examples/stm32f334/Cargo.toml +++ b/examples/stm32f334/Cargo.toml @@ -5,20 +5,20 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } nb = "1.0.0" embedded-storage = "0.3.1" diff --git a/examples/stm32f334/src/bin/opamp.rs b/examples/stm32f334/src/bin/opamp.rs index 2dbf1bdab..c344935d7 100644 --- a/examples/stm32f334/src/bin/opamp.rs +++ b/examples/stm32f334/src/bin/opamp.rs @@ -4,7 +4,7 @@ use defmt::info; use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, SampleTime}; -use embassy_stm32::opamp::{OpAmp, OpAmpGain}; +use embassy_stm32::opamp::OpAmp; use embassy_stm32::peripherals::ADC2; use embassy_stm32::time::mhz; use embassy_stm32::{adc, bind_interrupts, Config}; @@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) -> ! { let mut vrefint = adc.enable_vref(); let mut temperature = adc.enable_temperature(); - let mut buffer = opamp.buffer_ext(&mut p.PA7, &mut p.PA6, OpAmpGain::Mul1); + let mut buffer = opamp.buffer_ext(p.PA7.reborrow(), p.PA6.reborrow()); loop { let vref = adc.read(&mut vrefint).await; diff --git a/examples/stm32f334/src/bin/pwm.rs b/examples/stm32f334/src/bin/pwm.rs index e6d1a6c02..2b0686121 100644 --- a/examples/stm32f334/src/bin/pwm.rs +++ b/examples/stm32f334/src/bin/pwm.rs @@ -57,14 +57,14 @@ async fn main(_spawner: Spawner) { // embassy_stm32::pac::HRTIM1 // .tim(0) // .setr(0) - // .modify(|w| w.set_sst(Activeeffect::SETACTIVE)); + // .modify(|w| w.set_sst(true)); // // Timer::after_millis(500).await; // // embassy_stm32::pac::HRTIM1 // .tim(0) // .rstr(0) - // .modify(|w| w.set_srt(Inactiveeffect::SETINACTIVE)); + // .modify(|w| w.set_srt(true)); let max_duty = buck_converter.get_max_compare_value(); diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index b85361596..edab9ea00 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -6,17 +6,17 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32f429zi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt" ] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } -embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-tim4", "exti", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt" ] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } +embassy-net-wiznet = { version = "0.2.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" @@ -24,9 +24,10 @@ embedded-hal = "0.2.6" embedded-hal-bus = { version = "0.2", features = ["async"] } embedded-io = { version = "0.6.0" } embedded-io-async = { version = "0.6.1" } -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } futures-util = { version = "0.3.30", default-features = false } heapless = { version = "0.8", default-features = false } +critical-section = "1.1" nb = "1.0.0" embedded-storage = "0.3.1" micromath = "2.0.0" diff --git a/examples/stm32f4/src/bin/can.rs b/examples/stm32f4/src/bin/can.rs index 8e3beee24..fd90e0d6d 100644 --- a/examples/stm32f4/src/bin/can.rs +++ b/examples/stm32f4/src/bin/can.rs @@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) { // To synchronise to the bus the RX input needs to see a high level. // Use `mem::forget()` to release the borrow on the pin but keep the // pull-up resistor enabled. - let rx_pin = Input::new(&mut p.PA11, Pull::Up); + let rx_pin = Input::new(p.PA11.reborrow(), Pull::Up); core::mem::forget(rx_pin); let mut can = Can::new(p.CAN1, p.PA11, p.PA12, Irqs); diff --git a/examples/stm32f4/src/bin/dac.rs b/examples/stm32f4/src/bin/dac.rs index dd2a45718..68fe6cabd 100644 --- a/examples/stm32f4/src/bin/dac.rs +++ b/examples/stm32f4/src/bin/dac.rs @@ -4,7 +4,6 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dac::{DacCh1, Value}; -use embassy_stm32::dma::NoDma; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,7 +11,7 @@ async fn main(_spawner: Spawner) -> ! { let p = embassy_stm32::init(Default::default()); info!("Hello World, dude!"); - let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + let mut dac = DacCh1::new_blocking(p.DAC1, p.PA4); loop { for v in 0..=255 { diff --git a/examples/stm32f4/src/bin/eth.rs b/examples/stm32f4/src/bin/eth.rs index 9388c64bf..634d8e2c6 100644 --- a/examples/stm32f4/src/bin/eth.rs +++ b/examples/stm32f4/src/bin/eth.rs @@ -4,9 +4,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; -use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::{Ethernet, GenericPhy, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::Hertz; @@ -21,11 +20,11 @@ bind_interrupts!(struct Irqs { HASH_RNG => rng::InterruptHandler; }); -type Device = Ethernet<'static, ETH, GenericSMI>; +type Device = Ethernet<'static, ETH, GenericPhy>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -76,7 +75,7 @@ async fn main(spawner: Spawner) -> ! { p.PG13, p.PB13, p.PG11, - GenericSMI::new(0), + GenericPhy::new_auto(), mac_addr, ); @@ -88,12 +87,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -105,7 +103,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 4096]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32f4/src/bin/eth_compliance_test.rs b/examples/stm32f4/src/bin/eth_compliance_test.rs new file mode 100644 index 000000000..52f9d57f6 --- /dev/null +++ b/examples/stm32f4/src/bin/eth_compliance_test.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::eth::{Ethernet, GenericPhy, PacketQueue, StationManagement}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + HASH_RNG => rng::InterruptHandler; +}); + +#[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::MUL180, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 180 / 2 = 180Mhz. + divq: None, + 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); + + info!("Hello Compliance World!"); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + const PHY_ADDR: u8 = 0; + static PACKETS: StaticCell> = StaticCell::new(); + let mut device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericPhy::new(PHY_ADDR), + mac_addr, + ); + + let sm = device.station_management(); + + // Just an example. Exact register settings depend on the specific PHY and test. + sm.smi_write(PHY_ADDR, 0, 0x2100); + sm.smi_write(PHY_ADDR, 11, 0xA000); + + // NB: Remember to reset the PHY after testing before starting the networking stack + + loop { + Timer::after_secs(1).await; + } +} diff --git a/examples/stm32f4/src/bin/eth_w5500.rs b/examples/stm32f4/src/bin/eth_w5500.rs index 5c3c6c3ba..6e6bef08c 100644 --- a/examples/stm32f4/src/bin/eth_w5500.rs +++ b/examples/stm32f4/src/bin/eth_w5500.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_net_wiznet::chip::W5500; use embassy_net_wiznet::{Device, Runner, State}; use embassy_stm32::exti::ExtiInput; @@ -31,8 +31,8 @@ async fn ethernet_task(runner: Runner<'static, W5500, EthernetSPI, ExtiInput<'st } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -92,12 +92,11 @@ async fn main(spawner: Spawner) -> ! { // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; diff --git a/examples/stm32f4/src/bin/flash_async.rs b/examples/stm32f4/src/bin/flash_async.rs index 493a536f3..755713542 100644 --- a/examples/stm32f4/src/bin/flash_async.rs +++ b/examples/stm32f4/src/bin/flash_async.rs @@ -3,9 +3,9 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_stm32::bind_interrupts; use embassy_stm32::flash::{Flash, InterruptHandler}; -use embassy_stm32::gpio::{AnyPin, Level, Output, Pin, Speed}; +use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; +use embassy_stm32::{bind_interrupts, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -21,14 +21,14 @@ async fn main(spawner: Spawner) { let mut f = Flash::new(p.FLASH, Irqs); // Led should blink uninterrupted during ~2sec erase operation - spawner.spawn(blinky(p.PB7.degrade())).unwrap(); + spawner.spawn(blinky(p.PB7.into())).unwrap(); // Test on bank 2 in order not to stall CPU. test_flash(&mut f, 1024 * 1024, 128 * 1024).await; } #[embassy_executor::task] -async fn blinky(p: AnyPin) { +async fn blinky(p: Peri<'static, AnyPin>) { let mut led = Output::new(p, Level::High, Speed::Low); loop { diff --git a/examples/stm32f4/src/bin/i2s_dma.rs b/examples/stm32f4/src/bin/i2s_dma.rs index 27b165f1b..db5103d0f 100644 --- a/examples/stm32f4/src/bin/i2s_dma.rs +++ b/examples/stm32f4/src/bin/i2s_dma.rs @@ -1,34 +1,83 @@ +// This example is written for an STM32F411 chip communicating with an external +// PCM5102a DAC. Remap pins, change clock speeds, etc. as necessary for your own +// hardware. +// +// NOTE: This example outputs potentially loud audio. Please run responsibly. + #![no_std] #![no_main] -use core::fmt::Write; - -use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::i2s::{Config, I2S}; +use embassy_stm32::i2s::{Config, Format, I2S}; use embassy_stm32::time::Hertz; -use heapless::String; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); - info!("Hello World!"); + let config = { + use embassy_stm32::rcc::*; - let mut i2s = I2S::new_txonly( - p.SPI2, - p.PC3, // sd - p.PB12, // ws - p.PB10, // ck - p.PC6, // mck - p.DMA1_CH4, - Hertz(1_000_000), - Config::default(), + let mut config = embassy_stm32::Config::default(); + config.rcc.hse = Some(Hse { + freq: Hertz::mhz(25), + mode: HseMode::Oscillator, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV25, + mul: PllMul::MUL192, + divp: Some(PllPDiv::DIV2), + divq: Some(PllQDiv::DIV4), + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; + + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + + // reference your chip's manual for proper clock settings; this config + // is recommended for a 32 bit frame at 48 kHz sample rate + config.rcc.plli2s = Some(Pll { + prediv: PllPreDiv::DIV25, + mul: PllMul::MUL384, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV5), + }); + config.enable_debug_during_sleep = true; + + config + }; + + let p = embassy_stm32::init(config); + + // stereo wavetable generation + let mut wavetable = [0u16; 1200]; + for (i, frame) in wavetable.chunks_mut(2).enumerate() { + frame[0] = ((((i / 150) % 2) * 2048) as i16 - 1024) as u16; // 160 Hz square wave in left channel + frame[1] = ((((i / 100) % 2) * 2048) as i16 - 1024) as u16; // 240 Hz square wave in right channel + } + + // i2s configuration + let mut dma_buffer = [0u16; 2400]; + + let mut i2s_config = Config::default(); + i2s_config.format = Format::Data16Channel32; + i2s_config.master_clock = false; + let mut i2s = I2S::new_txonly_nomck( + p.SPI3, + p.PB5, // sd + p.PA15, // ws + p.PB3, // ck + p.DMA1_CH7, + &mut dma_buffer, + Hertz(48_000), + i2s_config, ); + i2s.start(); - for n in 0u32.. { - let mut write: String<128> = String::new(); - core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); - i2s.write(&mut write.as_bytes()).await.ok(); + loop { + i2s.write(&wavetable).await.ok(); } } diff --git a/examples/stm32f4/src/bin/input_capture.rs b/examples/stm32f4/src/bin/input_capture.rs index 49de33d2b..fe5e2bdfc 100644 --- a/examples/stm32f4/src/bin/input_capture.rs +++ b/examples/stm32f4/src/bin/input_capture.rs @@ -7,14 +7,14 @@ use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_stm32::time::khz; use embassy_stm32::timer::input_capture::{CapturePin, InputCapture}; use embassy_stm32::timer::{self, Channel}; -use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::{bind_interrupts, peripherals, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; /// Connect PB2 and PB10 with a 1k Ohm resistor #[embassy_executor::task] -async fn blinky(led: peripherals::PB2) { +async fn blinky(led: Peri<'static, peripherals::PB2>) { let mut led = Output::new(led, Level::High, Speed::Low); loop { diff --git a/examples/stm32f4/src/bin/pwm.rs b/examples/stm32f4/src/bin/pwm.rs index 8844a9f0e..04811162b 100644 --- a/examples/stm32f4/src/bin/pwm.rs +++ b/examples/stm32f4/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -15,22 +14,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PE9, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PE9, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32f4/src/bin/pwm_input.rs b/examples/stm32f4/src/bin/pwm_input.rs index ce200549d..465cbe4f5 100644 --- a/examples/stm32f4/src/bin/pwm_input.rs +++ b/examples/stm32f4/src/bin/pwm_input.rs @@ -6,14 +6,14 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_stm32::time::khz; use embassy_stm32::timer::pwm_input::PwmInput; -use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_stm32::{bind_interrupts, peripherals, timer, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; /// Connect PB2 and PA6 with a 1k Ohm resistor #[embassy_executor::task] -async fn blinky(led: peripherals::PB2) { +async fn blinky(led: Peri<'static, peripherals::PB2>) { let mut led = Output::new(led, Level::High, Speed::Low); loop { diff --git a/examples/stm32f4/src/bin/sdmmc.rs b/examples/stm32f4/src/bin/sdmmc.rs index 66e4e527c..e97b63925 100644 --- a/examples/stm32f4/src/bin/sdmmc.rs +++ b/examples/stm32f4/src/bin/sdmmc.rs @@ -59,7 +59,7 @@ async fn main(_spawner: Spawner) { let mut err = None; loop { - match sdmmc.init_card(mhz(24)).await { + match sdmmc.init_sd_card(mhz(24)).await { Ok(_) => break, Err(e) => { if err != Some(e) { diff --git a/examples/stm32f4/src/bin/usart_buffered.rs b/examples/stm32f4/src/bin/usart_buffered.rs index c99807f11..3b6cdad8d 100644 --- a/examples/stm32f4/src/bin/usart_buffered.rs +++ b/examples/stm32f4/src/bin/usart_buffered.rs @@ -21,7 +21,7 @@ async fn main(_spawner: Spawner) { let mut tx_buf = [0u8; 32]; let mut rx_buf = [0u8; 32]; - let mut buf_usart = BufferedUart::new(p.USART3, Irqs, p.PD9, p.PD8, &mut tx_buf, &mut rx_buf, config).unwrap(); + let mut buf_usart = BufferedUart::new(p.USART3, p.PD9, p.PD8, &mut tx_buf, &mut rx_buf, Irqs, config).unwrap(); loop { let buf = buf_usart.fill_buf().await.unwrap(); diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs index 94e51c338..322cb90c7 100644 --- a/examples/stm32f4/src/bin/usb_ethernet.rs +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::rng::{self, Rng}; use embassy_stm32::time::Hertz; use embassy_stm32::usb::Driver; @@ -31,8 +31,8 @@ async fn usb_ncm_task(class: Runner<'static, UsbDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } bind_interrupts!(struct Irqs { @@ -93,12 +93,6 @@ async fn main(spawner: Spawner) { config.max_power = 100; config.max_packet_size_0 = 64; - // Required for Windows support. - config.composite_with_iads = true; - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - // Create embassy-usb DeviceBuilder using the driver and config. static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); @@ -144,11 +138,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/stm32f4/src/bin/usb_hid_keyboard.rs b/examples/stm32f4/src/bin/usb_hid_keyboard.rs index 1270995c4..d6b4a9bc9 100644 --- a/examples/stm32f4/src/bin/usb_hid_keyboard.rs +++ b/examples/stm32f4/src/bin/usb_hid_keyboard.rs @@ -71,13 +71,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/stm32f4/src/bin/usb_hid_mouse.rs b/examples/stm32f4/src/bin/usb_hid_mouse.rs index 45136f965..badb65e98 100644 --- a/examples/stm32f4/src/bin/usb_hid_mouse.rs +++ b/examples/stm32f4/src/bin/usb_hid_mouse.rs @@ -66,13 +66,6 @@ async fn main(_spawner: Spawner) { 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 config_descriptor = [0; 256]; diff --git a/examples/stm32f4/src/bin/usb_raw.rs b/examples/stm32f4/src/bin/usb_raw.rs index b2d706208..bbbcc082b 100644 --- a/examples/stm32f4/src/bin/usb_raw.rs +++ b/examples/stm32f4/src/bin/usb_raw.rs @@ -119,13 +119,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-raw 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 config_descriptor = [0; 256]; diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs index 328b5effe..e62b2d8d6 100644 --- a/examples/stm32f4/src/bin/usb_serial.rs +++ b/examples/stm32f4/src/bin/usb_serial.rs @@ -66,13 +66,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-serial example"); config.serial_number = Some("12345678"); - // Required for windows compatibility. - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/stm32f4/src/bin/usb_uac_speaker.rs b/examples/stm32f4/src/bin/usb_uac_speaker.rs new file mode 100644 index 000000000..654bec498 --- /dev/null +++ b/examples/stm32f4/src/bin/usb_uac_speaker.rs @@ -0,0 +1,383 @@ +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::signal::Signal; +use embassy_sync::zerocopy_channel; +use embassy_usb::class::uac1; +use embassy_usb::class::uac1::speaker::{self, Speaker}; +use embassy_usb::driver::EndpointError; +use heapless::Vec; +use micromath::F32Ext; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +static TIMER: Mutex>>> = + Mutex::new(RefCell::new(None)); + +// A counter signal that is written by the feedback timer, once every `FEEDBACK_REFRESH_PERIOD`. +// At that point, a feedback value is sent to the host. +pub static FEEDBACK_SIGNAL: Signal = Signal::new(); + +// Stereo input +pub const INPUT_CHANNEL_COUNT: usize = 2; + +// This example uses a fixed sample rate of 48 kHz. +pub const SAMPLE_RATE_HZ: u32 = 48_000; +pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 42_000_000; + +// Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality. +pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; +pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); +pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; +pub const SAMPLE_SIZE_PER_S: usize = (SAMPLE_RATE_HZ as usize) * INPUT_CHANNEL_COUNT * SAMPLE_SIZE; + +// Size of audio samples per 1 ms - for the full-speed USB frame period of 1 ms. +pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); + +// Select front left and right audio channels. +pub const AUDIO_CHANNELS: [uac1::Channel; INPUT_CHANNEL_COUNT] = [uac1::Channel::LeftFront, uac1::Channel::RightFront]; + +// Factor of two as a margin for feedback (this is an excessive amount) +pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; +pub const USB_MAX_SAMPLE_COUNT: usize = USB_MAX_PACKET_SIZE / SAMPLE_SIZE; + +// The data type that is exchanged via the zero-copy channel (a sample vector). +pub type SampleBlock = Vec; + +// Feedback is provided in 10.14 format for full-speed endpoints. +pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; +const FEEDBACK_SHIFT: usize = 14; + +const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Sends feedback messages to the host. +async fn feedback_handler<'d, T: usb::Instance + 'd>( + feedback: &mut speaker::Feedback<'d, usb::Driver<'d, T>>, + feedback_factor: f32, +) -> Result<(), Disconnected> { + let mut packet: Vec = Vec::new(); + + // Collects the fractional component of the feedback value that is lost by rounding. + let mut rest = 0.0_f32; + + loop { + let counter = FEEDBACK_SIGNAL.wait().await; + + packet.clear(); + + let raw_value = counter as f32 * feedback_factor + rest; + let value = raw_value.round(); + rest = raw_value - value; + + let value = value as u32; + packet.push(value as u8).unwrap(); + packet.push((value >> 8) as u8).unwrap(); + packet.push((value >> 16) as u8).unwrap(); + + feedback.write_packet(&packet).await?; + } +} + +/// Handles streaming of audio data from the host. +async fn stream_handler<'d, T: usb::Instance + 'd>( + stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>, + sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) -> Result<(), Disconnected> { + loop { + let mut usb_data = [0u8; USB_MAX_PACKET_SIZE]; + let data_size = stream.read_packet(&mut usb_data).await?; + + let word_count = data_size / SAMPLE_SIZE; + + if word_count * SAMPLE_SIZE == data_size { + // Obtain a buffer from the channel + let samples = sender.send().await; + samples.clear(); + + for w in 0..word_count { + let byte_offset = w * SAMPLE_SIZE; + let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap()); + + // Fill the sample buffer with data. + samples.push(sample).unwrap(); + } + + sender.send_done(); + } else { + debug!("Invalid USB buffer size of {}, skipped.", data_size); + } + } +} + +/// Receives audio samples from the USB streaming task and can play them back. +#[embassy_executor::task] +async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) { + loop { + let _samples = usb_audio_receiver.receive().await; + // Use the samples, for example play back via the SAI peripheral. + + // Notify the channel that the buffer is now ready to be reused + usb_audio_receiver.receive_done(); + } +} + +/// Receives audio samples from the host. +#[embassy_executor::task] +async fn usb_streaming_task( + mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>, + mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) { + loop { + stream.wait_connection().await; + _ = stream_handler(&mut stream, &mut sender).await; + } +} + +/// Sends sample rate feedback to the host. +/// +/// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that +/// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format. +/// +/// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors. +/// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz, +/// 24.576 MHz would be one such option. +/// +/// A good choice for the STM32F4, which also has to generate a 48 MHz clock from its HSE (e.g. running at 8 MHz) +/// for USB, is to clock the feedback timer from the MCLK output of the SAI peripheral. The SAI peripheral then uses an +/// external clock. In that case, wiring the MCLK output to the timer clock input is required. +/// +/// This simple example just uses the internal clocks for supplying the feedback timer, +/// and does not even set up a SAI peripheral. +#[embassy_executor::task] +async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) { + let feedback_factor = + ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32; + + // Should be 2.3405714285714287... + info!("Using a feedback factor of {}.", feedback_factor); + + loop { + feedback.wait_connection().await; + _ = feedback_handler(&mut feedback, feedback_factor).await; + } +} + +#[embassy_executor::task] +async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) { + usb_device.run().await; +} + +/// Checks for changes on the control monitor of the class. +/// +/// In this case, monitor changes of volume or mute state. +#[embassy_executor::task] +async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) { + loop { + control_monitor.changed().await; + + for channel in AUDIO_CHANNELS { + let volume = control_monitor.volume(channel).unwrap(); + info!("Volume changed to {} on channel {}.", volume, channel); + } + } +} + +/// Feedback value measurement and calculation +/// +/// Used for measuring/calculating the number of samples that were received from the host during the +/// `FEEDBACK_REFRESH_PERIOD`. +/// +/// Configured in this example with +/// - a refresh period of 8 ms, and +/// - a tick rate of 42 MHz. +/// +/// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`. +#[interrupt] +fn TIM2() { + static LAST_TICKS: Mutex> = Mutex::new(Cell::new(0)); + static FRAME_COUNT: Mutex> = Mutex::new(Cell::new(0)); + + critical_section::with(|cs| { + // Read timer counter. + let timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32(); + + let status = timer.sr().read(); + + const CHANNEL_INDEX: usize = 0; + if status.ccif(CHANNEL_INDEX) { + let ticks = timer.ccr(CHANNEL_INDEX).read(); + + let frame_count = FRAME_COUNT.borrow(cs); + let last_ticks = LAST_TICKS.borrow(cs); + + frame_count.set(frame_count.get() + 1); + if frame_count.get() >= FEEDBACK_REFRESH_PERIOD.frame_count() { + frame_count.set(0); + FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(last_ticks.get())); + last_ticks.set(ticks); + } + }; + + // Clear trigger interrupt flag. + timer.sr().modify(|r| r.set_tif(false)); + }); +} + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + 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), // ((8 MHz / 4) * 168) / 2 = 168 Mhz. + divq: Some(PllQDiv::DIV7), // ((8 MHz / 4) * 168) / 7 = 48 Mhz. + 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; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Configure all required buffers in a static way. + debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); + + static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); + let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); + + const CONTROL_BUF_SIZE: usize = 64; + static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); + let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); + + const FEEDBACK_BUF_SIZE: usize = 4; + static EP_OUT_BUFFER: StaticCell<[u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]> = + StaticCell::new(); + let ep_out_buffer = EP_OUT_BUFFER.init([0u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(speaker::State::new()); + + // Create the driver, from the HAL. + let mut usb_config = usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + usb_config.vbus_detection = false; + + let usb_driver = usb::Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, usb_config); + + // Basic USB device configuration + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-audio-speaker example"); + config.serial_number = Some("12345678"); + + let mut builder = embassy_usb::Builder::new( + usb_driver, + config, + config_descriptor, + bos_descriptor, + &mut [], // no msos descriptors + control_buf, + ); + + // Create the UAC1 Speaker class components + let (stream, feedback, control_monitor) = Speaker::new( + &mut builder, + state, + USB_MAX_PACKET_SIZE as u16, + uac1::SampleWidth::Width4Byte, + &[SAMPLE_RATE_HZ], + &AUDIO_CHANNELS, + FEEDBACK_REFRESH_PERIOD, + ); + + // Create the USB device + let usb_device = builder.build(); + + // Establish a zero-copy channel for transferring received audio samples between tasks + static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); + let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); + let (sender, receiver) = channel.split(); + + // Run a timer for counting between SOF interrupts. + let mut tim2 = timer::low_level::Timer::new(p.TIM2); + tim2.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); + tim2.set_trigger_source(timer::low_level::TriggerSource::ITR1); // The USB SOF signal. + + const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1; + tim2.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC); + tim2.set_input_capture_prescaler(TIMER_CHANNEL, 0); + tim2.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2); + + // Reset all interrupt flags. + tim2.regs_gp32().sr().write(|r| r.0 = 0); + + // Enable routing of SOF to the timer. + tim2.regs_gp32().or().write(|r| *r = 0b10 << 10); + + tim2.enable_channel(TIMER_CHANNEL, true); + tim2.enable_input_interrupt(TIMER_CHANNEL, true); + + tim2.start(); + + TIMER.lock(|p| p.borrow_mut().replace(tim2)); + + // Unmask the TIM2 interrupt. + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::TIM2); + } + + // Launch USB audio tasks. + unwrap!(spawner.spawn(usb_control_task(control_monitor))); + unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); + unwrap!(spawner.spawn(usb_feedback_task(feedback))); + unwrap!(spawner.spawn(usb_task(usb_device))); + unwrap!(spawner.spawn(audio_receiver_task(receiver))); +} diff --git a/examples/stm32f4/src/bin/ws2812_pwm.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs index cbaff75fc..ca924e181 100644 --- a/examples/stm32f4/src/bin/ws2812_pwm.rs +++ b/examples/stm32f4/src/bin/ws2812_pwm.rs @@ -61,7 +61,7 @@ async fn main(_spawner: Spawner) { // construct ws2812 non-return-to-zero (NRZ) code bit by bit // ws2812 only need 24 bits for each LED, but we add one bit more to keep PWM output low - let max_duty = ws2812_pwm.get_max_duty() as u16; + let max_duty = ws2812_pwm.max_duty_cycle(); let n0 = 8 * max_duty / 25; // ws2812 Bit 0 high level timing let n1 = 2 * n0; // ws2812 Bit 1 high level timing @@ -84,7 +84,7 @@ async fn main(_spawner: Spawner) { let pwm_channel = Channel::Ch1; // make sure PWM output keep low on first start - ws2812_pwm.set_duty(pwm_channel, 0); + ws2812_pwm.channel(pwm_channel).set_duty_cycle(0); // flip color at 2 Hz let mut ticker = Ticker::every(Duration::from_millis(500)); @@ -92,7 +92,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.waveform_up(&mut dp.DMA1_CH2, pwm_channel, color).await; + ws2812_pwm.waveform_up(dp.DMA1_CH2.reborrow(), 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/stm32f469/Cargo.toml b/examples/stm32f469/Cargo.toml index 6a5bd0b29..87a3b8f75 100644 --- a/examples/stm32f469/Cargo.toml +++ b/examples/stm32f469/Cargo.toml @@ -6,17 +6,17 @@ license = "MIT OR Apache-2.0" [dependencies] # Specific examples only for stm32f469 -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f469ni", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32f469ni", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "1.0.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } [profile.release] debug = 2 diff --git a/examples/stm32f469/src/bin/dsi_bsp.rs b/examples/stm32f469/src/bin/dsi_bsp.rs index e4e9e9c01..3a24d5dcf 100644 --- a/examples/stm32f469/src/bin/dsi_bsp.rs +++ b/examples/stm32f469/src/bin/dsi_bsp.rs @@ -363,20 +363,20 @@ async fn main(_spawner: Spawner) { const _PCPOLARITY: bool = false; // LTDC_PCPOLARITY_IPC == 0 const LTDC_DE_POLARITY: Depol = if !DE_POLARITY { - Depol::ACTIVELOW + Depol::ACTIVE_LOW } else { - Depol::ACTIVEHIGH + Depol::ACTIVE_HIGH }; const LTDC_VS_POLARITY: Vspol = if !VS_POLARITY { - Vspol::ACTIVEHIGH + Vspol::ACTIVE_HIGH } else { - Vspol::ACTIVELOW + Vspol::ACTIVE_LOW }; const LTDC_HS_POLARITY: Hspol = if !HS_POLARITY { - Hspol::ACTIVEHIGH + Hspol::ACTIVE_HIGH } else { - Hspol::ACTIVELOW + Hspol::ACTIVE_LOW }; /* Timing Configuration */ @@ -397,7 +397,7 @@ async fn main(_spawner: Spawner) { w.set_hspol(LTDC_HS_POLARITY); w.set_vspol(LTDC_VS_POLARITY); w.set_depol(LTDC_DE_POLARITY); - w.set_pcpol(Pcpol::RISINGEDGE); + w.set_pcpol(Pcpol::RISING_EDGE); }); // Set Synchronization size diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 8c591ebd2..c5801ea90 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -6,31 +6,30 @@ license = "MIT OR Apache-2.0" [dependencies] # 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.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti", "single-bank"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embedded-io-async = { version = "0.6.1" } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } nb = "1.0.0" -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"] } +aes-gcm = { version = "0.10.3", default-features = false, features = ["aes", "heapless"] } [profile.release] debug = 2 diff --git a/examples/stm32f7/src/bin/can.rs b/examples/stm32f7/src/bin/can.rs index a82e335a9..58ba940a8 100644 --- a/examples/stm32f7/src/bin/can.rs +++ b/examples/stm32f7/src/bin/can.rs @@ -42,7 +42,7 @@ async fn main(spawner: Spawner) { // To synchronise to the bus the RX input needs to see a high level. // Use `mem::forget()` to release the borrow on the pin but keep the // pull-up resistor enabled. - let rx_pin = Input::new(&mut p.PA15, Pull::Up); + let rx_pin = Input::new(p.PA15.reborrow(), Pull::Up); core::mem::forget(rx_pin); static CAN: StaticCell> = StaticCell::new(); diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 2fd10c8fb..67a2b34bb 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs @@ -4,16 +4,14 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; -use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::{Ethernet, GenericPhy, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::Hertz; use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; use embassy_time::Timer; use embedded_io_async::Write; -use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -22,11 +20,11 @@ bind_interrupts!(struct Irqs { HASH_RNG => rng::InterruptHandler; }); -type Device = Ethernet<'static, ETH, GenericSMI>; +type Device = Ethernet<'static, ETH, GenericPhy>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -77,7 +75,7 @@ async fn main(spawner: Spawner) -> ! { p.PG13, p.PB13, p.PG11, - GenericSMI::new(0), + GenericPhy::new_auto(), mac_addr, ); @@ -89,12 +87,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -106,7 +103,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 4096]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32f7/src/bin/qspi.rs b/examples/stm32f7/src/bin/qspi.rs index 90d319b7a..bd3287964 100644 --- a/examples/stm32f7/src/bin/qspi.rs +++ b/examples/stm32f7/src/bin/qspi.rs @@ -72,7 +72,7 @@ impl FlashMemory { address: None, dummy: DummyCycles::_0, }; - self.qspi.command(transaction); + self.qspi.blocking_command(transaction); } pub fn reset_memory(&mut self) { @@ -143,7 +143,7 @@ impl FlashMemory { dummy: DummyCycles::_0, }; self.enable_write(); - self.qspi.command(transaction); + self.qspi.blocking_command(transaction); self.wait_write_finish(); } diff --git a/examples/stm32f7/src/bin/sdmmc.rs b/examples/stm32f7/src/bin/sdmmc.rs index 6d36ef518..787bef25e 100644 --- a/examples/stm32f7/src/bin/sdmmc.rs +++ b/examples/stm32f7/src/bin/sdmmc.rs @@ -54,7 +54,7 @@ async fn main(_spawner: Spawner) { // Should print 400kHz for initialization info!("Configured clock: {}", sdmmc.clock().0); - unwrap!(sdmmc.init_card(mhz(25)).await); + unwrap!(sdmmc.init_sd_card(mhz(25)).await); let card = unwrap!(sdmmc.card()); diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs index 1906b28ed..349012888 100644 --- a/examples/stm32f7/src/bin/usb_serial.rs +++ b/examples/stm32f7/src/bin/usb_serial.rs @@ -66,13 +66,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-serial example"); config.serial_number = Some("12345678"); - // Required for windows compatibility. - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index a50074ce0..bf1e7250e 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -6,20 +6,20 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32g0b1re to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g0b1re", "memory-x", "unstable-pac", "exti"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g0b1re", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32g0/src/bin/adc_dma.rs b/examples/stm32g0/src/bin/adc_dma.rs index 3713e5a21..d7515933c 100644 --- a/examples/stm32g0/src/bin/adc_dma.rs +++ b/examples/stm32g0/src/bin/adc_dma.rs @@ -25,7 +25,7 @@ async fn main(_spawner: Spawner) { loop { adc.read( - &mut dma, + dma.reborrow(), [ (&mut vrefint_channel, SampleTime::CYCLES160_5), (&mut pa0, SampleTime::CYCLES160_5), diff --git a/examples/stm32g0/src/bin/hf_timer.rs b/examples/stm32g0/src/bin/hf_timer.rs index 3ea06cdee..dfb6e0edc 100644 --- a/examples/stm32g0/src/bin/hf_timer.rs +++ b/examples/stm32g0/src/bin/hf_timer.rs @@ -16,7 +16,9 @@ async fn main(_spawner: Spawner) { let mut config = PeripheralConfig::default(); { use embassy_stm32::rcc::*; - config.rcc.hsi = true; + config.rcc.hsi = Some(Hsi { + sys_div: HsiSysDiv::DIV1, + }); config.rcc.pll = Some(Pll { source: PllSource::HSI, prediv: PllPreDiv::DIV1, diff --git a/examples/stm32g0/src/bin/input_capture.rs b/examples/stm32g0/src/bin/input_capture.rs index 69fdae96d..08df4e043 100644 --- a/examples/stm32g0/src/bin/input_capture.rs +++ b/examples/stm32g0/src/bin/input_capture.rs @@ -16,14 +16,14 @@ use embassy_stm32::time::khz; use embassy_stm32::timer::input_capture::{CapturePin, InputCapture}; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; use embassy_stm32::timer::Channel; -use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_stm32::{bind_interrupts, peripherals, timer, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; // Connect PB1 and PA6 with a 1k Ohm resistor #[embassy_executor::task] -async fn blinky(led: peripherals::PB1) { +async fn blinky(led: Peri<'static, peripherals::PB1>) { let mut led = Output::new(led, Level::High, Speed::Low); loop { @@ -47,10 +47,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(blinky(p.PB1))); // Connect PB1 and PA8 with a 1k Ohm resistor - let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default()); - pwm.enable(Channel::Ch1); - pwm.set_duty(Channel::Ch1, 50); + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().enable(); + pwm.ch1().set_duty_cycle(50); let ch1 = CapturePin::new_ch1(p.PA0, Pull::None); let mut ic = InputCapture::new(p.TIM2, Some(ch1), None, None, None, Irqs, khz(1000), Default::default()); diff --git a/examples/stm32g0/src/bin/onewire_ds18b20.rs b/examples/stm32g0/src/bin/onewire_ds18b20.rs new file mode 100644 index 000000000..62f8711a6 --- /dev/null +++ b/examples/stm32g0/src/bin/onewire_ds18b20.rs @@ -0,0 +1,274 @@ +//! This examples shows how you can use buffered or DMA UART to read a DS18B20 temperature sensor on 1-Wire bus. +//! Connect 5k pull-up resistor between PA9 and 3.3V. +#![no_std] +#![no_main] + +use cortex_m::singleton; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::mode::Async; +use embassy_stm32::usart::{ + BufferedUartRx, BufferedUartTx, Config, ConfigError, OutputConfig, RingBufferedUartRx, UartTx, +}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +/// Create onewire bus using DMA USART +fn create_onewire(p: embassy_stm32::Peripherals) -> OneWire, RingBufferedUartRx<'static>> { + use embassy_stm32::usart::Uart; + bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; + }); + + let mut config = Config::default(); + config.tx_config = OutputConfig::OpenDrain; + + let usart = Uart::new_half_duplex( + p.USART1, + p.PA9, + Irqs, + p.DMA1_CH1, + p.DMA1_CH2, + config, + // Enable readback so we can read sensor pulling data low while transmission is in progress + usart::HalfDuplexReadback::Readback, + ) + .unwrap(); + + const BUFFER_SIZE: usize = 16; + let (tx, rx) = usart.split(); + let rx_buf: &mut [u8; BUFFER_SIZE] = singleton!(TX_BUF: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap(); + let rx = rx.into_ring_buffered(rx_buf); + OneWire::new(tx, rx) +} + +/* +/// Create onewire bus using buffered USART +fn create_onewire(p: embassy_stm32::Peripherals) -> OneWire, BufferedUartRx<'static>> { + use embassy_stm32::usart::BufferedUart; + bind_interrupts!(struct Irqs { + USART1 => usart::BufferedInterruptHandler; + }); + + const BUFFER_SIZE: usize = 16; + let mut config = Confi::default(); + config.tx_config = OutputConfig::OpenDrain; + let tx_buf: &mut [u8; BUFFER_SIZE] = singleton!(TX_BUF: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap(); + let rx_buf: &mut [u8; BUFFER_SIZE] = singleton!(RX_BUF: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]).unwrap(); + let usart = BufferedUart::new_half_duplex( + p.USART1, + p.PA9, + Irqs, + tx_buf, + rx_buf, + config, + // Enable readback so we can read sensor pulling data low while transmission is in progress + usart::HalfDuplexReadback::Readback, + ) + .unwrap(); + let (tx, rx) = usart.split(); + OneWire::new(tx, rx) +} +*/ + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let onewire = create_onewire(p); + let mut sensor = Ds18b20::new(onewire); + + loop { + // Start a new temperature measurement + sensor.start().await; + // Wait for the measurement to finish + Timer::after(Duration::from_secs(1)).await; + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after(Duration::from_secs(1)).await; + } +} + +pub trait SetBaudrate { + fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError>; +} +impl SetBaudrate for BufferedUartTx<'_> { + fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { + BufferedUartTx::set_baudrate(self, baudrate) + } +} +impl SetBaudrate for BufferedUartRx<'_> { + fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { + BufferedUartRx::set_baudrate(self, baudrate) + } +} +impl SetBaudrate for RingBufferedUartRx<'_> { + fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { + RingBufferedUartRx::set_baudrate(self, baudrate) + } +} +impl SetBaudrate for UartTx<'_, Async> { + fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { + UartTx::set_baudrate(self, baudrate) + } +} + +/// Simplified OneWire bus driver +pub struct OneWire +where + TX: embedded_io_async::Write + SetBaudrate, + RX: embedded_io_async::Read + SetBaudrate, +{ + tx: TX, + rx: RX, +} + +impl OneWire +where + TX: embedded_io_async::Write + SetBaudrate, + RX: embedded_io_async::Read + SetBaudrate, +{ + // bitrate with one bit taking ~104 us + const RESET_BUADRATE: u32 = 9600; + // bitrate with one bit taking ~8.7 us + const BAUDRATE: u32 = 115200; + + // startbit + 8 low bits = 9 * 1/115200 = 78 us low pulse + const LOGIC_1_CHAR: u8 = 0xFF; + // startbit only = 1/115200 = 8.7 us low pulse + const LOGIC_0_CHAR: u8 = 0x00; + + // Address all devices on the bus + const COMMAND_SKIP_ROM: u8 = 0xCC; + + pub fn new(tx: TX, rx: RX) -> Self { + Self { tx, rx } + } + + fn set_baudrate(&mut self, baudrate: u32) -> Result<(), ConfigError> { + self.tx.set_baudrate(baudrate)?; + self.rx.set_baudrate(baudrate) + } + + /// Reset the bus by at least 480 us low pulse. + pub async fn reset(&mut self) { + // Switch to 9600 baudrate, so one bit takes ~104 us + self.set_baudrate(Self::RESET_BUADRATE).expect("set_baudrate failed"); + // Low USART start bit + 4x low bits = 5 * 104 us = 520 us low pulse + self.tx.write(&[0xF0]).await.expect("write failed"); + + // Read the value on the bus + let mut buffer = [0; 1]; + self.rx.read_exact(&mut buffer).await.expect("read failed"); + + // Switch back to 115200 baudrate, so one bit takes ~8.7 us + self.set_baudrate(Self::BAUDRATE).expect("set_baudrate failed"); + + // read and expect sensor pulled some high bits to low (device present) + if buffer[0] & 0xF != 0 || buffer[0] & 0xF0 == 0xF0 { + warn!("No device present"); + } + } + + /// Send byte and read response on the bus. + pub async fn write_read_byte(&mut self, byte: u8) -> u8 { + // One byte is sent as 8 UART characters + let mut tx = [0; 8]; + for (pos, char) in tx.iter_mut().enumerate() { + *char = if (byte >> pos) & 0x1 == 0x1 { + Self::LOGIC_1_CHAR + } else { + Self::LOGIC_0_CHAR + }; + } + self.tx.write_all(&tx).await.expect("write failed"); + + // Readback the value on the bus, sensors can pull logic 1 to 0 + let mut rx = [0; 8]; + self.rx.read_exact(&mut rx).await.expect("read failed"); + let mut bus_byte = 0; + for (pos, char) in rx.iter().enumerate() { + // if its 0xFF, sensor didnt pull the bus to low level + if *char == 0xFF { + bus_byte |= 1 << pos; + } + } + + bus_byte + } + + /// Read a byte from the bus. + pub async fn read_byte(&mut self) -> u8 { + self.write_read_byte(0xFF).await + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20 +where + TX: embedded_io_async::Write + SetBaudrate, + RX: embedded_io_async::Read + SetBaudrate, +{ + bus: OneWire, +} + +impl Ds18b20 +where + TX: embedded_io_async::Write + SetBaudrate, + RX: embedded_io_async::Read + SetBaudrate, +{ + /// Start a temperature conversion. + const FN_CONVERT_T: u8 = 0x44; + /// Read contents of the scratchpad containing the temperature. + const FN_READ_SCRATCHPAD: u8 = 0xBE; + + pub fn new(bus: OneWire) -> Self { + Self { bus } + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.bus.reset().await; + self.bus.write_read_byte(OneWire::::COMMAND_SKIP_ROM).await; + self.bus.write_read_byte(Self::FN_CONVERT_T).await; + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.bus.reset().await; + self.bus.write_read_byte(OneWire::::COMMAND_SKIP_ROM).await; + self.bus.write_read_byte(Self::FN_READ_SCRATCHPAD).await; + + let mut data = [0; 9]; + for byte in data.iter_mut() { + *byte = self.bus.read_byte().await; + } + + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u16) << 8 | data[0] as u16) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/examples/stm32g0/src/bin/pwm_input.rs b/examples/stm32g0/src/bin/pwm_input.rs index 152ecda86..9d6b5fe97 100644 --- a/examples/stm32g0/src/bin/pwm_input.rs +++ b/examples/stm32g0/src/bin/pwm_input.rs @@ -14,14 +14,13 @@ use embassy_stm32::gpio::{Level, Output, OutputType, Pull, Speed}; use embassy_stm32::time::khz; use embassy_stm32::timer::pwm_input::PwmInput; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; -use embassy_stm32::{bind_interrupts, peripherals, timer}; +use embassy_stm32::{bind_interrupts, peripherals, timer, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; // Connect PB1 and PA6 with a 1k Ohm resistor #[embassy_executor::task] -async fn blinky(led: peripherals::PB1) { +async fn blinky(led: Peri<'static, peripherals::PB1>) { let mut led = Output::new(led, Level::High, Speed::Low); loop { @@ -43,11 +42,10 @@ async fn main(spawner: Spawner) { unwrap!(spawner.spawn(blinky(p.PB1))); // Connect PA8 and PA6 with a 1k Ohm resistor - let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(1), Default::default()); - let max = pwm.get_max_duty(); - pwm.set_duty(Channel::Ch1, max / 4); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PA8, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(1), Default::default()); + pwm.ch1().set_duty_cycle_fraction(1, 4); + pwm.ch1().enable(); let mut pwm_input = PwmInput::new(p.TIM2, p.PA0, Pull::None, khz(1000)); pwm_input.enable(); diff --git a/examples/stm32g0/src/bin/rtc.rs b/examples/stm32g0/src/bin/rtc.rs index c02c1ecd7..50fb6398e 100644 --- a/examples/stm32g0/src/bin/rtc.rs +++ b/examples/stm32g0/src/bin/rtc.rs @@ -15,7 +15,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let now = DateTime::from(2023, 6, 14, DayOfWeek::Friday, 15, 59, 10); + let now = DateTime::from(2023, 6, 14, DayOfWeek::Friday, 15, 59, 10, 0); let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); diff --git a/examples/stm32g0/src/bin/usart_buffered.rs b/examples/stm32g0/src/bin/usart_buffered.rs index c097a0c5a..6d9ec8cb4 100644 --- a/examples/stm32g0/src/bin/usart_buffered.rs +++ b/examples/stm32g0/src/bin/usart_buffered.rs @@ -21,7 +21,7 @@ async fn main(_spawner: Spawner) { config.baudrate = 115200; let mut tx_buf = [0u8; 256]; let mut rx_buf = [0u8; 256]; - let mut usart = BufferedUart::new(p.USART1, Irqs, p.PB7, p.PB6, &mut tx_buf, &mut rx_buf, config).unwrap(); + let mut usart = BufferedUart::new(p.USART1, p.PB7, p.PB6, &mut tx_buf, &mut rx_buf, Irqs, config).unwrap(); usart.write_all(b"Hello Embassy World!\r\n").await.unwrap(); info!("wrote Hello, starting echo"); diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 2768147a1..3d2c2aa7d 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -6,22 +6,22 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32g491re to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } usbd-hid = "0.8.1" -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" 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"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } static_cell = "2.0.0" diff --git a/examples/stm32g4/src/bin/adc_differential.rs b/examples/stm32g4/src/bin/adc_differential.rs new file mode 100644 index 000000000..78d071d45 --- /dev/null +++ b/examples/stm32g4/src/bin/adc_differential.rs @@ -0,0 +1,47 @@ +//! adc differential mode example +//! +//! This example uses adc1 in differential mode +//! p:pa0 n:pa1 + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +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(); + { + 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); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES247_5); + adc.set_differential(&mut p.PA0, true); //p:pa0,n:pa1 + + // can also use + // adc.set_differential_channel(1, true); + info!("adc initialized"); + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32g4/src/bin/adc_dma.rs b/examples/stm32g4/src/bin/adc_dma.rs new file mode 100644 index 000000000..202704085 --- /dev/null +++ b/examples/stm32g4/src/bin/adc_dma.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, AdcChannel as _, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +static mut DMA_BUF: [u16; 2] = [0; 2]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut read_buffer = unsafe { &mut DMA_BUF[..] }; + + let mut config = Config::default(); + { + 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 p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + + let mut dma = p.DMA1_CH1; + let mut vrefint_channel = adc.enable_vrefint().degrade_adc(); + let mut pa0 = p.PA0.degrade_adc(); + + loop { + adc.read( + dma.reborrow(), + [ + (&mut vrefint_channel, SampleTime::CYCLES247_5), + (&mut pa0, SampleTime::CYCLES247_5), + ] + .into_iter(), + &mut read_buffer, + ) + .await; + + let vrefint = read_buffer[0]; + let measured = read_buffer[1]; + info!("vrefint: {}", vrefint); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32g4/src/bin/adc_oversampling.rs b/examples/stm32g4/src/bin/adc_oversampling.rs new file mode 100644 index 000000000..d31eb20f8 --- /dev/null +++ b/examples/stm32g4/src/bin/adc_oversampling.rs @@ -0,0 +1,57 @@ +//! adc oversampling example +//! +//! This example uses adc oversampling to achieve 16bit data + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::vals::{Rovsm, Trovs}; +use embassy_stm32::adc::{Adc, SampleTime}; +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(); + { + 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); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES6_5); + // From https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // page652 Oversampler + // Table 172. Maximum output results vs N and M. Grayed values indicates truncation + // 0x00 oversampling ratio X2 + // 0x01 oversampling ratio X4 + // 0x02 oversampling ratio X8 + // 0x03 oversampling ratio X16 + // 0x04 oversampling ratio X32 + // 0x05 oversampling ratio X64 + // 0x06 oversampling ratio X128 + // 0x07 oversampling ratio X256 + adc.set_oversampling_ratio(0x03); // ratio X3 + adc.set_oversampling_shift(0b0000); // no shift + adc.enable_regular_oversampling_mode(Rovsm::RESUMED, Trovs::AUTOMATIC, true); + + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: 0x{:X}", measured); //max 0xFFF0 -> 65520 + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs index d4809a481..6c965012c 100644 --- a/examples/stm32g4/src/bin/pwm.rs +++ b/examples/stm32g4/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -15,22 +14,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PC0, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PC0, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index 7caea634f..2e87d3931 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4); + let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4, Default::default()); ucpd.cc_phy().set_pull(CcPull::Sink); info!("Waiting for USB connection..."); diff --git a/examples/stm32g4/src/bin/usb_serial.rs b/examples/stm32g4/src/bin/usb_serial.rs index ed2ac7fac..9f66f0c53 100644 --- a/examples/stm32g4/src/bin/usb_serial.rs +++ b/examples/stm32g4/src/bin/usb_serial.rs @@ -51,11 +51,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-Serial Example"); config.serial_number = Some("123456"); - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 64]; diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 30b1d2be9..f3fda7ff3 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -6,16 +6,16 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32h563zi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "low-power"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "low-power"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" @@ -23,10 +23,9 @@ embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-io-async = { version = "0.6.1" } -embedded-nal-async = { version = "0.7.1" } -panic-probe = { version = "0.3", features = ["print-defmt"] } +embedded-nal-async = "0.8.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } -rand_core = "0.6.3" critical-section = "1.1" micromath = "2.0.0" stm32-fmc = "0.3.0" diff --git a/examples/stm32h5/src/bin/adc.rs b/examples/stm32h5/src/bin/adc.rs new file mode 100644 index 000000000..c5d508ece --- /dev/null +++ b/examples/stm32h5/src/bin/adc.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +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(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV4), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: None, + divq: None, + divr: Some(PllDiv::DIV4), // 100mhz + }); + config.rcc.sys = Sysclk::PLL1_P; // 200 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV1; // 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.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcdacsel = mux::Adcdacsel::PLL2_R; + } + let mut p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC1); + + adc.set_sample_time(SampleTime::CYCLES24_5); + + let mut vrefint_channel = adc.enable_vrefint(); + + loop { + let vrefint = adc.blocking_read(&mut vrefint_channel); + info!("vrefint: {}", vrefint); + let measured = adc.blocking_read(&mut p.PA0); + info!("measured: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32h5/src/bin/cordic.rs b/examples/stm32h5/src/bin/cordic.rs index 73e873574..cbf854704 100644 --- a/examples/stm32h5/src/bin/cordic.rs +++ b/examples/stm32h5/src/bin/cordic.rs @@ -11,7 +11,7 @@ async fn main(_spawner: Spawner) { let mut dp = embassy_stm32::init(Default::default()); let mut cordic = cordic::Cordic::new( - &mut dp.CORDIC, + dp.CORDIC.reborrow(), unwrap!(cordic::Config::new( cordic::Function::Sin, Default::default(), @@ -59,8 +59,8 @@ async fn main(_spawner: Spawner) { let cnt1 = unwrap!( cordic .async_calc_32bit( - &mut dp.GPDMA1_CH0, - &mut dp.GPDMA1_CH1, + dp.GPDMA1_CH0.reborrow(), + dp.GPDMA1_CH1.reborrow(), &input_buf[..arg1.len() - 1], // limit input buf to its actual length &mut output_u32, true, diff --git a/examples/stm32h5/src/bin/dts.rs b/examples/stm32h5/src/bin/dts.rs new file mode 100644 index 000000000..8c18fafea --- /dev/null +++ b/examples/stm32h5/src/bin/dts.rs @@ -0,0 +1,75 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dts::{Dts, InterruptHandler, SampleTime}; +use embassy_stm32::peripherals::DTS; +use embassy_stm32::rcc::frequency; +use embassy_stm32::{bind_interrupts, dts, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + DTS => InterruptHandler; +}); + +#[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.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV4), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: None, + divq: None, + divr: Some(PllDiv::DIV4), // 100mhz + }); + config.rcc.sys = Sysclk::PLL1_P; // 200 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV1; // 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.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcdacsel = mux::Adcdacsel::PLL2_R; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut config = dts::Config::default(); + config.sample_time = SampleTime::ClockCycles15; + let mut dts = Dts::new(p.DTS, Irqs, config); + + let cal = Dts::factory_calibration(); + let convert_to_celsius = |raw_temp: u16| { + let raw_temp = raw_temp as f32; + let sample_time = (config.sample_time as u8) as f32; + + let f = frequency::().0 as f32; + + let t0 = cal.t0 as f32; + let fmt0 = cal.fmt0.0 as f32; + let ramp_coeff = cal.ramp_coeff as f32; + + ((f * sample_time / raw_temp) - fmt0) / ramp_coeff + t0 + }; + + loop { + let temp = dts.read().await; + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32h5/src/bin/eth.rs b/examples/stm32h5/src/bin/eth.rs index 65cfad8c9..1d85cc1e7 100644 --- a/examples/stm32h5/src/bin/eth.rs +++ b/examples/stm32h5/src/bin/eth.rs @@ -4,9 +4,8 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; -use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::{Ethernet, GenericPhy, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rcc::{ AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, VoltageScale, @@ -16,7 +15,6 @@ use embassy_stm32::time::Hertz; use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; use embassy_time::Timer; use embedded_io_async::Write; -use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -25,11 +23,11 @@ bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; }); -type Device = Ethernet<'static, ETH, GenericSMI>; +type Device = Ethernet<'static, ETH, GenericPhy>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -80,7 +78,7 @@ async fn main(spawner: Spawner) -> ! { p.PG13, p.PB15, p.PG11, - GenericSMI::new(0), + GenericPhy::new_auto(), mac_addr, ); @@ -92,12 +90,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -109,7 +106,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 1024]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32h5/src/bin/stop.rs b/examples/stm32h5/src/bin/stop.rs index 0d14c0668..e650791c5 100644 --- a/examples/stm32h5/src/bin/stop.rs +++ b/examples/stm32h5/src/bin/stop.rs @@ -10,7 +10,7 @@ use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; use embassy_stm32::low_power::Executor; use embassy_stm32::rcc::{HSIPrescaler, LsConfig}; use embassy_stm32::rtc::{Rtc, RtcConfig}; -use embassy_stm32::Config; +use embassy_stm32::{Config, Peri}; use embassy_time::Timer; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -48,7 +48,7 @@ async fn async_main(spawner: Spawner) { } #[embassy_executor::task] -async fn blinky(led: AnyPin) { +async fn blinky(led: Peri<'static, AnyPin>) { let mut led = Output::new(led, Level::Low, Speed::Low); loop { info!("high"); diff --git a/examples/stm32h5/src/bin/usb_c_pd.rs b/examples/stm32h5/src/bin/usb_c_pd.rs new file mode 100644 index 000000000..acb03e498 --- /dev/null +++ b/examples/stm32h5/src/bin/usb_c_pd.rs @@ -0,0 +1,93 @@ +//! This example targets the NUCLEO-H563ZI platform. +//! USB-C CC lines are protected by a TCPP01-M12 chipset. +#![no_std] +#![no_main] + +use defmt::{error, info, Format}; +use embassy_executor::Spawner; +use embassy_stm32::gpio::Output; +use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, Ucpd}; +use embassy_stm32::{bind_interrupts, peripherals, Config}; +use embassy_time::{with_timeout, Duration}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UCPD1 => ucpd::InterruptHandler; +}); + +#[derive(Debug, Format)] +enum CableOrientation { + Normal, + Flipped, + DebugAccessoryMode, +} + +// Returns true when the cable +async fn wait_attached(cc_phy: &mut CcPhy<'_, T>) -> CableOrientation { + loop { + let (cc1, cc2) = cc_phy.vstate(); + if cc1 == CcVState::LOWEST && cc2 == CcVState::LOWEST { + // Detached, wait until attached by monitoring the CC lines. + cc_phy.wait_for_vstate_change().await; + continue; + } + + // Attached, wait for CC lines to be stable for tCCDebounce (100..200ms). + if with_timeout(Duration::from_millis(100), cc_phy.wait_for_vstate_change()) + .await + .is_ok() + { + // State has changed, restart detection procedure. + continue; + }; + + // State was stable for the complete debounce period, check orientation. + return match (cc1, cc2) { + (_, CcVState::LOWEST) => CableOrientation::Normal, // CC1 connected + (CcVState::LOWEST, _) => CableOrientation::Flipped, // CC2 connected + _ => CableOrientation::DebugAccessoryMode, // Both connected (special cable) + }; + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let config = Config::default(); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB13, p.PB14, Default::default()); + ucpd.cc_phy().set_pull(CcPull::Sink); + + // This pin controls the dead-battery mode on the attached TCPP01-M12. + // If low, TCPP01-M12 disconnects CC lines and presents dead-battery resistance on CC lines, thus set high. + // Must only be set after the CC pull is established. + let _tcpp01_m12_ndb = Output::new(p.PA9, embassy_stm32::gpio::Level::High, embassy_stm32::gpio::Speed::Low); + + info!("Waiting for USB connection..."); + let cable_orientation = wait_attached(ucpd.cc_phy()).await; + info!("USB cable connected, orientation: {}", cable_orientation); + + let cc_sel = match cable_orientation { + CableOrientation::Normal => { + info!("Starting PD communication on CC1 pin"); + CcSel::CC1 + } + CableOrientation::Flipped => { + info!("Starting PD communication on CC2 pin"); + CcSel::CC2 + } + CableOrientation::DebugAccessoryMode => panic!("No PD communication in DAM"), + }; + let (_cc_phy, mut pd_phy) = ucpd.split_pd_phy(p.GPDMA1_CH0, p.GPDMA1_CH1, cc_sel); + + loop { + // Enough space for the longest non-extended data message. + let mut buf = [0_u8; 30]; + match pd_phy.receive(buf.as_mut()).await { + Ok(n) => info!("USB PD RX: {=[u8]:?}", &buf[..n]), + Err(e) => error!("USB PD RX: {}", e), + } + } +} diff --git a/examples/stm32h5/src/bin/usb_serial.rs b/examples/stm32h5/src/bin/usb_serial.rs index fbcbdb5f9..e8f536133 100644 --- a/examples/stm32h5/src/bin/usb_serial.rs +++ b/examples/stm32h5/src/bin/usb_serial.rs @@ -56,13 +56,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-serial example"); config.serial_number = Some("12345678"); - // Required for windows compatibility. - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/stm32h5/src/bin/usb_uac_speaker.rs b/examples/stm32h5/src/bin/usb_uac_speaker.rs new file mode 100644 index 000000000..5d007261c --- /dev/null +++ b/examples/stm32h5/src/bin/usb_uac_speaker.rs @@ -0,0 +1,374 @@ +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::signal::Signal; +use embassy_sync::zerocopy_channel; +use embassy_usb::class::uac1; +use embassy_usb::class::uac1::speaker::{self, Speaker}; +use embassy_usb::driver::EndpointError; +use heapless::Vec; +use micromath::F32Ext; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_DRD_FS => usb::InterruptHandler; +}); + +static TIMER: Mutex>>> = + Mutex::new(RefCell::new(None)); + +// A counter signal that is written by the feedback timer, once every `FEEDBACK_REFRESH_PERIOD`. +// At that point, a feedback value is sent to the host. +pub static FEEDBACK_SIGNAL: Signal = Signal::new(); + +// Stereo input +pub const INPUT_CHANNEL_COUNT: usize = 2; + +// This example uses a fixed sample rate of 48 kHz. +pub const SAMPLE_RATE_HZ: u32 = 48_000; +pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 31_250_000; + +// Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality. +pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; +pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); +pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; +pub const SAMPLE_SIZE_PER_S: usize = (SAMPLE_RATE_HZ as usize) * INPUT_CHANNEL_COUNT * SAMPLE_SIZE; + +// Size of audio samples per 1 ms - for the full-speed USB frame period of 1 ms. +pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); + +// Select front left and right audio channels. +pub const AUDIO_CHANNELS: [uac1::Channel; INPUT_CHANNEL_COUNT] = [uac1::Channel::LeftFront, uac1::Channel::RightFront]; + +// Factor of two as a margin for feedback (this is an excessive amount) +pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; +pub const USB_MAX_SAMPLE_COUNT: usize = USB_MAX_PACKET_SIZE / SAMPLE_SIZE; + +// The data type that is exchanged via the zero-copy channel (a sample vector). +pub type SampleBlock = Vec; + +// Feedback is provided in 10.14 format for full-speed endpoints. +pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; +const FEEDBACK_SHIFT: usize = 14; + +const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Sends feedback messages to the host. +async fn feedback_handler<'d, T: usb::Instance + 'd>( + feedback: &mut speaker::Feedback<'d, usb::Driver<'d, T>>, + feedback_factor: f32, +) -> Result<(), Disconnected> { + let mut packet: Vec = Vec::new(); + + // Collects the fractional component of the feedback value that is lost by rounding. + let mut rest = 0.0_f32; + + loop { + let counter = FEEDBACK_SIGNAL.wait().await; + + packet.clear(); + + let raw_value = counter as f32 * feedback_factor + rest; + let value = raw_value.round(); + rest = raw_value - value; + + let value = value as u32; + + debug!("Feedback value: {}", value); + + packet.push(value as u8).unwrap(); + packet.push((value >> 8) as u8).unwrap(); + packet.push((value >> 16) as u8).unwrap(); + + feedback.write_packet(&packet).await?; + } +} + +/// Handles streaming of audio data from the host. +async fn stream_handler<'d, T: usb::Instance + 'd>( + stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>, + sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) -> Result<(), Disconnected> { + loop { + let mut usb_data = [0u8; USB_MAX_PACKET_SIZE]; + let data_size = stream.read_packet(&mut usb_data).await?; + + let word_count = data_size / SAMPLE_SIZE; + + if word_count * SAMPLE_SIZE == data_size { + // Obtain a buffer from the channel + let samples = sender.send().await; + samples.clear(); + + for w in 0..word_count { + let byte_offset = w * SAMPLE_SIZE; + let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap()); + + // Fill the sample buffer with data. + samples.push(sample).unwrap(); + } + + sender.send_done(); + } else { + debug!("Invalid USB buffer size of {}, skipped.", data_size); + } + } +} + +/// Receives audio samples from the USB streaming task and can play them back. +#[embassy_executor::task] +async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) { + loop { + let _samples = usb_audio_receiver.receive().await; + // Use the samples, for example play back via the SAI peripheral. + + // Notify the channel that the buffer is now ready to be reused + usb_audio_receiver.receive_done(); + } +} + +/// Receives audio samples from the host. +#[embassy_executor::task] +async fn usb_streaming_task( + mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB>>, + mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) { + loop { + stream.wait_connection().await; + info!("USB connected."); + _ = stream_handler(&mut stream, &mut sender).await; + info!("USB disconnected."); + } +} + +/// Sends sample rate feedback to the host. +/// +/// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that +/// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format. +/// +/// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors. +/// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz, +/// 24.576 MHz would be one such option. +#[embassy_executor::task] +async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB>>) { + let feedback_factor = + ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32; + + loop { + feedback.wait_connection().await; + _ = feedback_handler(&mut feedback, feedback_factor).await; + } +} + +#[embassy_executor::task] +async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB>>) { + usb_device.run().await; +} + +/// Checks for changes on the control monitor of the class. +/// +/// In this case, monitor changes of volume or mute state. +#[embassy_executor::task] +async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) { + loop { + control_monitor.changed().await; + + for channel in AUDIO_CHANNELS { + let volume = control_monitor.volume(channel).unwrap(); + info!("Volume changed to {} on channel {}.", volume, channel); + } + } +} + +/// Feedback value measurement and calculation +/// +/// Used for measuring/calculating the number of samples that were received from the host during the +/// `FEEDBACK_REFRESH_PERIOD`. +/// +/// Configured in this example with +/// - a refresh period of 8 ms, and +/// - a tick rate of 42 MHz. +/// +/// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`. +#[interrupt] +fn TIM5() { + static LAST_TICKS: Mutex> = Mutex::new(Cell::new(0)); + static FRAME_COUNT: Mutex> = Mutex::new(Cell::new(0)); + + critical_section::with(|cs| { + // Read timer counter. + let timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32(); + + let status = timer.sr().read(); + + const CHANNEL_INDEX: usize = 0; + if status.ccif(CHANNEL_INDEX) { + let ticks = timer.ccr(CHANNEL_INDEX).read(); + + let frame_count = FRAME_COUNT.borrow(cs); + let last_ticks = LAST_TICKS.borrow(cs); + + frame_count.set(frame_count.get() + 1); + if frame_count.get() >= FEEDBACK_REFRESH_PERIOD.frame_count() { + frame_count.set(0); + FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(last_ticks.get())); + last_ticks.set(ticks); + } + }; + + // Clear trigger interrupt flag. + timer.sr().modify(|r| r.set_tif(false)); + }); +} + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), // 250 Mhz + divq: None, + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL123, + divp: Some(PllDiv::DIV20), // 12.3 Mhz, close to 12.288 MHz for 48 kHz audio + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV4; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.mux.usbsel = mux::Usbsel::HSI48; + config.rcc.mux.sai2sel = mux::Saisel::PLL2_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Configure all required buffers in a static way. + debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); + + static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); + let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); + + const CONTROL_BUF_SIZE: usize = 64; + static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); + let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(speaker::State::new()); + + let usb_driver = usb::Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Basic USB device configuration + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-audio-speaker example"); + config.serial_number = Some("12345678"); + + let mut builder = embassy_usb::Builder::new( + usb_driver, + config, + config_descriptor, + bos_descriptor, + &mut [], // no msos descriptors + control_buf, + ); + + // Create the UAC1 Speaker class components + let (stream, feedback, control_monitor) = Speaker::new( + &mut builder, + state, + USB_MAX_PACKET_SIZE as u16, + uac1::SampleWidth::Width4Byte, + &[SAMPLE_RATE_HZ], + &AUDIO_CHANNELS, + FEEDBACK_REFRESH_PERIOD, + ); + + // Create the USB device + let usb_device = builder.build(); + + // Establish a zero-copy channel for transferring received audio samples between tasks + static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); + let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); + let (sender, receiver) = channel.split(); + + // Run a timer for counting between SOF interrupts. + let mut tim5 = timer::low_level::Timer::new(p.TIM5); + tim5.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); + tim5.set_trigger_source(timer::low_level::TriggerSource::ITR12); // The USB SOF signal. + + const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1; + tim5.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC); + tim5.set_input_capture_prescaler(TIMER_CHANNEL, 0); + tim5.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2); + + // Reset all interrupt flags. + tim5.regs_gp32().sr().write(|r| r.0 = 0); + + tim5.enable_channel(TIMER_CHANNEL, true); + tim5.enable_input_interrupt(TIMER_CHANNEL, true); + + tim5.start(); + + TIMER.lock(|p| p.borrow_mut().replace(tim5)); + + // Unmask the TIM5 interrupt. + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::TIM5); + } + + // Launch USB audio tasks. + unwrap!(spawner.spawn(usb_control_task(control_monitor))); + unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); + unwrap!(spawner.spawn(usb_feedback_task(feedback))); + unwrap!(spawner.spawn(usb_task(usb_device))); + unwrap!(spawner.spawn(audio_receiver_task(receiver))); +} diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 13fce7dc7..27c59d980 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -6,28 +6,27 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32h743bi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } -rand_core = "0.6.3" critical-section = "1.1" micromath = "2.0.0" stm32-fmc = "0.3.0" diff --git a/examples/stm32h7/src/bin/adc_dma.rs b/examples/stm32h7/src/bin/adc_dma.rs index 0b905d227..f06b5d06e 100644 --- a/examples/stm32h7/src/bin/adc_dma.rs +++ b/examples/stm32h7/src/bin/adc_dma.rs @@ -8,7 +8,7 @@ use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".ram_d3"] +#[unsafe(link_section = ".ram_d3")] static mut DMA_BUF: [u16; 2] = [0; 2]; #[embassy_executor::main] @@ -57,7 +57,7 @@ async fn main(_spawner: Spawner) { loop { adc.read( - &mut dma, + dma.reborrow(), [ (&mut vrefint_channel, SampleTime::CYCLES387_5), (&mut pc0, SampleTime::CYCLES810_5), diff --git a/examples/stm32h7/src/bin/dac.rs b/examples/stm32h7/src/bin/dac.rs index a6f969aba..27df80336 100644 --- a/examples/stm32h7/src/bin/dac.rs +++ b/examples/stm32h7/src/bin/dac.rs @@ -4,7 +4,6 @@ use cortex_m_rt::entry; use defmt::*; use embassy_stm32::dac::{DacCh1, Value}; -use embassy_stm32::dma::NoDma; use embassy_stm32::Config; use {defmt_rtt as _, panic_probe as _}; @@ -44,7 +43,7 @@ fn main() -> ! { } let p = embassy_stm32::init(config); - let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + let mut dac = DacCh1::new_blocking(p.DAC1, p.PA4); loop { for v in 0..=255 { diff --git a/examples/stm32h7/src/bin/dac_dma.rs b/examples/stm32h7/src/bin/dac_dma.rs index 3a9887e3c..8314754bc 100644 --- a/examples/stm32h7/src/bin/dac_dma.rs +++ b/examples/stm32h7/src/bin/dac_dma.rs @@ -4,11 +4,13 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dac::{DacCh1, DacCh2, ValueArray}; +use embassy_stm32::mode::Async; use embassy_stm32::pac::timer::vals::Mms; -use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; +use embassy_stm32::peripherals::{DAC1, TIM6, TIM7}; use embassy_stm32::rcc::frequency; use embassy_stm32::time::Hertz; use embassy_stm32::timer::low_level::Timer; +use embassy_stm32::Peri; use micromath::F32Ext; use {defmt_rtt as _, panic_probe as _}; @@ -56,7 +58,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn dac_task1(tim: TIM6, mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { +async fn dac_task1(tim: Peri<'static, TIM6>, mut dac: DacCh1<'static, DAC1, Async>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM6 frequency is {}", frequency::()); @@ -99,7 +101,7 @@ async fn dac_task1(tim: TIM6, mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { } #[embassy_executor::task] -async fn dac_task2(tim: TIM7, mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { +async fn dac_task2(tim: Peri<'static, TIM7>, mut dac: DacCh2<'static, DAC1, Async>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM7 frequency is {}", frequency::()); diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs index b2f8ed91e..fc14c1a70 100644 --- a/examples/stm32h7/src/bin/eth.rs +++ b/examples/stm32h7/src/bin/eth.rs @@ -4,15 +4,13 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; -use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_net::{Ipv4Address, StackResources}; +use embassy_stm32::eth::{Ethernet, GenericPhy, 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 rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -21,11 +19,11 @@ bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; }); -type Device = Ethernet<'static, ETH, GenericSMI>; +type Device = Ethernet<'static, ETH, GenericPhy>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -79,7 +77,7 @@ async fn main(spawner: Spawner) -> ! { p.PG13, // TX_D0: Transmit Bit 0 p.PB13, // TX_D1: Transmit Bit 1 p.PG11, // TX_EN: Transmit Enable - GenericSMI::new(0), + GenericPhy::new_auto(), mac_addr, ); @@ -91,12 +89,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -108,7 +105,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 1024]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 274c24ab1..46301a478 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -1,19 +1,19 @@ #![no_std] #![no_main] +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + 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_net::StackResources; +use embassy_stm32::eth::{Ethernet, GenericPhy, 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 embedded_nal_async::TcpConnect; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -22,11 +22,11 @@ bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; }); -type Device = Ethernet<'static, ETH, GenericSMI>; +type Device = Ethernet<'static, ETH, GenericPhy>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -79,7 +79,7 @@ async fn main(spawner: Spawner) -> ! { p.PG13, p.PB13, p.PG11, - GenericSMI::new(0), + GenericPhy::new_auto(), mac_addr, ); @@ -91,12 +91,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -104,7 +103,7 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); - let client = TcpClient::new(&stack, &state); + let client = TcpClient::new(stack, &state); loop { // You need to start a server on the host machine, for example: `nc -l 8000` diff --git a/examples/stm32h7/src/bin/eth_client_mii.rs b/examples/stm32h7/src/bin/eth_client_mii.rs index aa6544f41..99cd1a158 100644 --- a/examples/stm32h7/src/bin/eth_client_mii.rs +++ b/examples/stm32h7/src/bin/eth_client_mii.rs @@ -1,19 +1,19 @@ #![no_std] #![no_main] +use core::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; + 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_net::StackResources; +use embassy_stm32::eth::{Ethernet, GenericPhy, 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 embedded_nal_async::TcpConnect; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -22,11 +22,11 @@ bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; }); -type Device = Ethernet<'static, ETH, GenericSMI>; +type Device = Ethernet<'static, ETH, GenericPhy>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -84,7 +84,7 @@ async fn main(spawner: Spawner) -> ! { p.PC2, p.PE2, p.PG11, - GenericSMI::new(1), + GenericPhy::new_auto(), mac_addr, ); info!("Device created"); @@ -97,12 +97,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -110,7 +109,7 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); - let client = TcpClient::new(&stack, &state); + let client = TcpClient::new(stack, &state); loop { // You need to start a server on the host machine, for example: `nc -l 8000` diff --git a/examples/stm32h7/src/bin/i2c_shared.rs b/examples/stm32h7/src/bin/i2c_shared.rs index 321b35a3e..ec5757284 100644 --- a/examples/stm32h7/src/bin/i2c_shared.rs +++ b/examples/stm32h7/src/bin/i2c_shared.rs @@ -10,8 +10,10 @@ use embassy_stm32::i2c::{self, I2c}; use embassy_stm32::mode::Async; use embassy_stm32::time::Hertz; use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::NoopMutex; use embassy_time::{Duration, Timer}; +use embedded_hal_1::i2c::I2c as _; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -31,7 +33,7 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn temperature(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { +async fn temperature(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { let mut data = [0u8; 2]; loop { @@ -48,7 +50,7 @@ async fn temperature(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { } #[embassy_executor::task] -async fn humidity(mut i2c: impl embedded_hal_1::i2c::I2c + 'static) { +async fn humidity(mut i2c: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) { let mut data = [0u8; 6]; loop { diff --git a/examples/stm32h7/src/bin/low_level_timer_api.rs b/examples/stm32h7/src/bin/low_level_timer_api.rs index b796996ea..8de31ea5b 100644 --- a/examples/stm32h7/src/bin/low_level_timer_api.rs +++ b/examples/stm32h7/src/bin/low_level_timer_api.rs @@ -7,7 +7,7 @@ use embassy_stm32::gpio::{AfType, Flex, OutputType, Speed}; use embassy_stm32::time::{khz, Hertz}; use embassy_stm32::timer::low_level::{OutputCompareMode, Timer as LLTimer}; use embassy_stm32::timer::{Channel, Channel1Pin, Channel2Pin, Channel3Pin, Channel4Pin, GeneralInstance32bit4Channel}; -use embassy_stm32::{into_ref, Config, Peripheral}; +use embassy_stm32::{Config, Peri}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -66,15 +66,13 @@ pub struct SimplePwm32<'d, T: GeneralInstance32bit4Channel> { impl<'d, T: GeneralInstance32bit4Channel> SimplePwm32<'d, T> { pub fn new( - tim: impl Peripheral

+ 'd, - ch1: impl Peripheral

> + 'd, - ch2: impl Peripheral

> + 'd, - ch3: impl Peripheral

> + 'd, - ch4: impl Peripheral

> + 'd, + tim: Peri<'d, T>, + ch1: Peri<'d, impl Channel1Pin>, + ch2: Peri<'d, impl Channel2Pin>, + ch3: Peri<'d, impl Channel3Pin>, + ch4: Peri<'d, impl Channel4Pin>, freq: Hertz, ) -> Self { - into_ref!(ch1, ch2, ch3, ch4); - let af1 = ch1.af_num(); let af2 = ch2.af_num(); let af3 = ch3.af_num(); diff --git a/examples/stm32h7/src/bin/pwm.rs b/examples/stm32h7/src/bin/pwm.rs index 1e48ba67b..a1c53fc3f 100644 --- a/examples/stm32h7/src/bin/pwm.rs +++ b/examples/stm32h7/src/bin/pwm.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; use embassy_stm32::time::khz; use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; -use embassy_stm32::timer::Channel; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -37,22 +36,22 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(config); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PA6, OutputType::PushPull); - let mut pwm = SimplePwm::new(p.TIM3, Some(ch1), None, None, None, khz(10), Default::default()); - let max = pwm.get_max_duty(); - pwm.enable(Channel::Ch1); + let ch1_pin = PwmPin::new_ch1(p.PA6, OutputType::PushPull); + let mut pwm = SimplePwm::new(p.TIM3, Some(ch1_pin), None, None, None, khz(10), Default::default()); + let mut ch1 = pwm.ch1(); + ch1.enable(); info!("PWM initialized"); - info!("PWM max duty {}", max); + info!("PWM max duty {}", ch1.max_duty_cycle()); loop { - pwm.set_duty(Channel::Ch1, 0); + ch1.set_duty_cycle_fully_off(); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 4); + ch1.set_duty_cycle_fraction(1, 4); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max / 2); + ch1.set_duty_cycle_fraction(1, 2); Timer::after_millis(300).await; - pwm.set_duty(Channel::Ch1, max - 1); + ch1.set_duty_cycle(ch1.max_duty_cycle() - 1); Timer::after_millis(300).await; } } diff --git a/examples/stm32h7/src/bin/sai.rs b/examples/stm32h7/src/bin/sai.rs index f6735e235..01937593a 100644 --- a/examples/stm32h7/src/bin/sai.rs +++ b/examples/stm32h7/src/bin/sai.rs @@ -16,9 +16,9 @@ const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks const SAMPLE_RATE: u32 = 48000; //DMA buffer must be in special region. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions -#[link_section = ".sram1_bss"] +#[unsafe(link_section = ".sram1_bss")] static mut TX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); -#[link_section = ".sram1_bss"] +#[unsafe(link_section = ".sram1_bss")] static mut RX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::main] @@ -81,8 +81,9 @@ async fn main(_spawner: Spawner) { rx_config.sync_output = false; let tx_buffer: &mut [u32] = unsafe { - TX_BUFFER.initialize_all_copied(0); - let (ptr, len) = TX_BUFFER.get_ptr_len(); + let buf = &mut *core::ptr::addr_of_mut!(TX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); core::slice::from_raw_parts_mut(ptr, len) }; @@ -98,21 +99,23 @@ async fn main(_spawner: Spawner) { ); let rx_buffer: &mut [u32] = unsafe { - RX_BUFFER.initialize_all_copied(0); - let (ptr, len) = RX_BUFFER.get_ptr_len(); + let buf = &mut *core::ptr::addr_of_mut!(RX_BUFFER); + buf.initialize_all_copied(0); + let (ptr, len) = buf.get_ptr_len(); core::slice::from_raw_parts_mut(ptr, len) }; let mut sai_receiver = Sai::new_synchronous(sub_block_rx, p.PE3, p.DMA1_CH1, rx_buffer, rx_config); - sai_receiver.start(); - sai_transmitter.start(); + sai_receiver.start().unwrap(); let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; loop { - sai_receiver.read(&mut buf).await.unwrap(); + // write() must be called before read() to start the master (transmitter) + // clock used by the receiver sai_transmitter.write(&buf).await.unwrap(); + sai_receiver.read(&mut buf).await.unwrap(); } } diff --git a/examples/stm32h7/src/bin/sdmmc.rs b/examples/stm32h7/src/bin/sdmmc.rs index abe2d4ba7..96840d8ff 100644 --- a/examples/stm32h7/src/bin/sdmmc.rs +++ b/examples/stm32h7/src/bin/sdmmc.rs @@ -53,7 +53,7 @@ async fn main(_spawner: Spawner) -> ! { // Should print 400kHz for initialization info!("Configured clock: {}", sdmmc.clock().0); - unwrap!(sdmmc.init_card(mhz(25)).await); + unwrap!(sdmmc.init_sd_card(mhz(25)).await); let card = unwrap!(sdmmc.card()); diff --git a/examples/stm32h7/src/bin/spi_bdma.rs b/examples/stm32h7/src/bin/spi_bdma.rs index 43fb6b41c..5a7dff572 100644 --- a/examples/stm32h7/src/bin/spi_bdma.rs +++ b/examples/stm32h7/src/bin/spi_bdma.rs @@ -16,16 +16,17 @@ use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; // Defined in memory.x -#[link_section = ".ram_d3"] +#[unsafe(link_section = ".ram_d3")] static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::task] async fn main_task(mut spi: spi::Spi<'static, Async>) { let (read_buffer, write_buffer) = unsafe { - RAM_D3.initialize_all_copied(0); + let ram = &mut *core::ptr::addr_of_mut!(RAM_D3); + ram.initialize_all_copied(0); ( - RAM_D3.get_subslice_mut_unchecked(0, 128), - RAM_D3.get_subslice_mut_unchecked(128, 128), + ram.get_subslice_mut_unchecked(0, 128), + ram.get_subslice_mut_unchecked(128, 128), ) }; diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs index 65ae597d4..50bb964da 100644 --- a/examples/stm32h7/src/bin/usb_serial.rs +++ b/examples/stm32h7/src/bin/usb_serial.rs @@ -67,13 +67,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-serial example"); config.serial_number = Some("12345678"); - // Required for windows compatibility. - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/stm32h723/.cargo/config.toml b/examples/stm32h723/.cargo/config.toml new file mode 100644 index 000000000..2e53663c5 --- /dev/null +++ b/examples/stm32h723/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H723ZGTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h723/Cargo.toml b/examples/stm32h723/Cargo.toml new file mode 100644 index 000000000..fb219733f --- /dev/null +++ b/examples/stm32h723/Cargo.toml @@ -0,0 +1,68 @@ +[package] +edition = "2021" +name = "embassy-stm32h723-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h723zg to your chip name, if necessary. +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h723zg", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +critical-section = "1.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h723/build.rs b/examples/stm32h723/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h723/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/stm32h723/memory.x b/examples/stm32h723/memory.x new file mode 100644 index 000000000..aa4c00505 --- /dev/null +++ b/examples/stm32h723/memory.x @@ -0,0 +1,106 @@ +MEMORY +{ + /* This file is intended for parts in the STM32H723 family. (RM0468) */ + /* - FLASH and RAM are mandatory memory sections. */ + /* - The sum of all non-FLASH sections must add to 564k total device RAM. */ + /* - The FLASH section size must match your device, see table below. */ + + /* FLASH */ + /* Select the appropriate FLASH size for your device. */ + /* - STM32H730xB 128K */ + /* - STM32H723xE/725xE 512K */ + /* - STM32H723xG/725xG/733xG/735xG 1M */ + FLASH1 : ORIGIN = 0x08000000, LENGTH = 1M + + /* Data TCM */ + /* - Two contiguous 64KB RAMs. */ + /* - Used for interrupt handlers, stacks and general RAM. */ + /* - Zero wait-states. */ + /* - The DTCM is taken as the origin of the base ram. (See below.) */ + /* This is also where the interrupt table and such will live, */ + /* which is required for deterministic performance. */ + DTCM : ORIGIN = 0x20000000, LENGTH = 128K + + /* Instruction TCM */ + /* - More memory can be assigned to ITCM. See AXI SRAM notes, below. */ + /* - Used for latency-critical interrupt handlers etc. */ + /* - Zero wait-states. */ + ITCM : ORIGIN = 0x00000000, LENGTH = 64K + 0K + + /* AXI SRAM */ + /* - AXISRAM is in D1 and accessible by all system masters except BDMA. */ + /* - Suitable for application data not stored in DTCM. */ + /* - Zero wait-states. */ + /* - The 192k of extra shared RAM is fully allotted to the AXI SRAM by default. */ + /* As a result: 64k (64k + 0k) for ITCM and 320k (128k + 192k) for AXI SRAM. */ + /* This can be re-configured via the TCM_AXI_SHARED[1,0] register when more */ + /* ITCM is required. */ + AXISRAM : ORIGIN = 0x24000000, LENGTH = 128K + 192K + + /* AHB SRAM */ + /* - SRAM1-2 are in D2 and accessible by all system masters except BDMA, LTDC */ + /* and SDMMC1. Suitable for use as DMA buffers. */ + /* - SRAM4 is in D3 and additionally accessible by the BDMA. Used for BDMA */ + /* buffers, for storing application data in lower-power modes. */ + /* - Zero wait-states. */ + SRAM1 : ORIGIN = 0x30000000, LENGTH = 16K + SRAM2 : ORIGIN = 0x30040000, LENGTH = 16K + SRAM4 : ORIGIN = 0x38000000, LENGTH = 16K + + /* Backup SRAM */ + /* Used to store data during low-power sleeps. */ + BSRAM : ORIGIN = 0x38800000, LENGTH = 4K +} + +/* +/* Assign the memory regions defined above for use. */ +/* + +/* Provide the mandatory FLASH and RAM definitions for cortex-m-rt's linker script. */ +REGION_ALIAS(FLASH, FLASH1); +REGION_ALIAS(RAM, DTCM); + +/* The location of the stack can be overridden using the `_stack_start` symbol. */ +/* - Set the stack location at the end of RAM, using all remaining space. */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + +/* The location of the .text section can be overridden using the */ +/* `_stext` symbol. By default it will place after .vector_table. */ +/* _stext = ORIGIN(FLASH) + 0x40c; */ + +/* Define sections for placing symbols into the extra memory regions above. */ +/* This makes them accessible from code. */ +/* - ITCM, DTCM and AXISRAM connect to a 64-bit wide bus -> align to 8 bytes. */ +/* - All other memories connect to a 32-bit wide bus -> align to 4 bytes. */ +SECTIONS { + .itcm (NOLOAD) : ALIGN(8) { + *(.itcm .itcm.*); + . = ALIGN(8); + } > ITCM + + .axisram (NOLOAD) : ALIGN(8) { + *(.axisram .axisram.*); + . = ALIGN(8); + } > AXISRAM + + .sram1 (NOLOAD) : ALIGN(4) { + *(.sram1 .sram1.*); + . = ALIGN(4); + } > SRAM1 + + .sram2 (NOLOAD) : ALIGN(4) { + *(.sram2 .sram2.*); + . = ALIGN(4); + } > SRAM2 + + .sram4 (NOLOAD) : ALIGN(4) { + *(.sram4 .sram4.*); + . = ALIGN(4); + } > SRAM4 + + .bsram (NOLOAD) : ALIGN(4) { + *(.bsram .bsram.*); + . = ALIGN(4); + } > BSRAM + +}; diff --git a/examples/stm32h723/src/bin/spdifrx.rs b/examples/stm32h723/src/bin/spdifrx.rs new file mode 100644 index 000000000..a04d7cb34 --- /dev/null +++ b/examples/stm32h723/src/bin/spdifrx.rs @@ -0,0 +1,175 @@ +//! This example receives inputs on SPDIFRX and outputs on SAI4. +//! +//! Only very few controllers connect the SPDIFRX symbol clock to a SAI peripheral's clock input. +//! However, this is necessary for synchronizing the symbol rates and avoiding glitches. +#![no_std] +#![no_main] + +use defmt::{info, trace}; +use embassy_executor::Spawner; +use embassy_futures::select::{self, select, Either}; +use embassy_stm32::spdifrx::{self, Spdifrx}; +use embassy_stm32::{bind_interrupts, peripherals, sai}; +use grounded::uninit::GroundedArrayCell; +use hal::sai::*; +use {defmt_rtt as _, embassy_stm32 as hal, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPDIF_RX => spdifrx::GlobalInterruptHandler; +}); + +const CHANNEL_COUNT: usize = 2; +const BLOCK_LENGTH: usize = 64; +const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * CHANNEL_COUNT; +const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks + +// DMA buffers must be in special regions. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions +#[unsafe(link_section = ".sram1")] +static mut SPDIFRX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[unsafe(link_section = ".sram4")] +static mut SAI_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut peripheral_config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + peripheral_config.rcc.hsi = Some(HSIPrescaler::DIV1); + peripheral_config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV16, + mul: PllMul::MUL200, + divp: Some(PllDiv::DIV2), // 400 MHz + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + peripheral_config.rcc.sys = Sysclk::PLL1_P; + peripheral_config.rcc.ahb_pre = AHBPrescaler::DIV2; + peripheral_config.rcc.apb1_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb2_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb3_pre = APBPrescaler::DIV2; + peripheral_config.rcc.apb4_pre = APBPrescaler::DIV2; + + peripheral_config.rcc.mux.spdifrxsel = mux::Spdifrxsel::PLL1_Q; + } + let mut p = embassy_stm32::init(peripheral_config); + + info!("SPDIFRX to SAI4 bridge"); + + // Use SPDIFRX clock for SAI. + // This ensures equal rates of sample production and consumption. + let clk_source = embassy_stm32::pac::rcc::vals::Saiasel::_RESERVED_5; + embassy_stm32::pac::RCC.d3ccipr().modify(|w| { + w.set_sai4asel(clk_source); + }); + + let sai_buffer: &mut [u32] = unsafe { + SAI_BUFFER.initialize_all_copied(0); + let (ptr, len) = SAI_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let spdifrx_buffer: &mut [u32] = unsafe { + SPDIFRX_BUFFER.initialize_all_copied(0); + let (ptr, len) = SPDIFRX_BUFFER.get_ptr_len(); + core::slice::from_raw_parts_mut(ptr, len) + }; + + let mut sai_transmitter = new_sai_transmitter( + p.SAI4.reborrow(), + p.PD13.reborrow(), + p.PC1.reborrow(), + p.PD12.reborrow(), + p.BDMA_CH0.reborrow(), + sai_buffer, + ); + let mut spdif_receiver = new_spdif_receiver( + p.SPDIFRX1.reborrow(), + p.PD7.reborrow(), + p.DMA2_CH7.reborrow(), + spdifrx_buffer, + ); + spdif_receiver.start(); + + let mut renew_sai = false; + loop { + let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; + + if renew_sai { + renew_sai = false; + trace!("Renew SAI."); + drop(sai_transmitter); + sai_transmitter = new_sai_transmitter( + p.SAI4.reborrow(), + p.PD13.reborrow(), + p.PC1.reborrow(), + p.PD12.reborrow(), + p.BDMA_CH0.reborrow(), + sai_buffer, + ); + } + + match select(spdif_receiver.read(&mut buf), sai_transmitter.wait_write_error()).await { + Either::First(spdif_read_result) => match spdif_read_result { + Ok(_) => (), + Err(spdifrx::Error::RingbufferError(_)) => { + trace!("SPDIFRX ringbuffer error. Renew."); + drop(spdif_receiver); + spdif_receiver = new_spdif_receiver( + p.SPDIFRX1.reborrow(), + p.PD7.reborrow(), + p.DMA2_CH7.reborrow(), + spdifrx_buffer, + ); + spdif_receiver.start(); + continue; + } + Err(spdifrx::Error::ChannelSyncError) => { + trace!("SPDIFRX channel sync (left/right assignment) error."); + continue; + } + }, + Either::Second(_) => { + renew_sai = true; + continue; + } + }; + + renew_sai = sai_transmitter.write(&buf).await.is_err(); + } +} + +/// Creates a new SPDIFRX instance for receiving sample data. +/// +/// Used (again) after dropping the SPDIFRX instance, in case of errors (e.g. source disconnect). +fn new_spdif_receiver<'d>( + spdifrx: &'d mut peripherals::SPDIFRX1, + input_pin: &'d mut peripherals::PD7, + dma: &'d mut peripherals::DMA2_CH7, + buf: &'d mut [u32], +) -> Spdifrx<'d, peripherals::SPDIFRX1> { + Spdifrx::new(spdifrx, Irqs, spdifrx::Config::default(), input_pin, dma, buf) +} + +/// Creates a new SAI4 instance for transmitting sample data. +/// +/// Used (again) after dropping the SAI4 instance, in case of errors (e.g. buffer overrun). +fn new_sai_transmitter<'d>( + sai: &'d mut peripherals::SAI4, + sck: &'d mut peripherals::PD13, + sd: &'d mut peripherals::PC1, + fs: &'d mut peripherals::PD12, + dma: &'d mut peripherals::BDMA_CH0, + buf: &'d mut [u32], +) -> Sai<'d, peripherals::SAI4, u32> { + let mut sai_config = hal::sai::Config::default(); + sai_config.slot_count = hal::sai::word::U4(CHANNEL_COUNT as u8); + sai_config.slot_enable = 0xFFFF; // All slots + sai_config.data_size = sai::DataSize::Data32; + sai_config.frame_length = (CHANNEL_COUNT * 32) as u8; + sai_config.master_clock_divider = hal::sai::MasterClockDivider::MasterClockDisabled; + + let (sub_block_tx, _) = hal::sai::split_subblocks(sai); + Sai::new_asynchronous(sub_block_tx, sck, sd, fs, dma, buf, sai_config) +} diff --git a/examples/stm32h735/Cargo.toml b/examples/stm32h735/Cargo.toml index 93e9575b6..8d23c346a 100644 --- a/examples/stm32h735/Cargo.toml +++ b/examples/stm32h735/Cargo.toml @@ -5,19 +5,19 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } embedded-graphics = { version = "0.8.1" } tinybmp = { version = "0.5" } diff --git a/examples/stm32h742/.cargo/config.toml b/examples/stm32h742/.cargo/config.toml new file mode 100644 index 000000000..f30a52a79 --- /dev/null +++ b/examples/stm32h742/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32H742VITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32H742VITx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h742/Cargo.toml b/examples/stm32h742/Cargo.toml new file mode 100644 index 000000000..31eff4379 --- /dev/null +++ b/examples/stm32h742/Cargo.toml @@ -0,0 +1,65 @@ +[package] +edition = "2021" +name = "embassy-stm32f7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32f777zi to your chip name, if necessary. +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ + "defmt", + "stm32h742vi", + "memory-x", + "unstable-pac", + "time-driver-any", + "exti", +] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = [ + "defmt", +] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = [ + "arch-cortex-m", + "executor-thread", + "defmt", +] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = [ + "defmt", + "defmt-timestamp-uptime", + "tick-hz-32_768", +] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = [ + "defmt", + "tcp", + "dhcpv4", + "medium-ethernet", +] } +embedded-io-async = { version = "0.6.1" } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = [ + "defmt", +] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.6", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +nb = "1.0.0" +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/stm32h742/build.rs b/examples/stm32h742/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32h742/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h742/src/bin/qspi.rs b/examples/stm32h742/src/bin/qspi.rs new file mode 100644 index 000000000..aee07f3f2 --- /dev/null +++ b/examples/stm32h742/src/bin/qspi.rs @@ -0,0 +1,292 @@ +#![no_std] +#![no_main] +#![allow(dead_code)] // Allow dead code as not all commands are used in the example + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::mode::Blocking; +use embassy_stm32::qspi::enums::{AddressSize, ChipSelectHighTime, FIFOThresholdLevel, MemorySize, *}; +use embassy_stm32::qspi::{Config as QspiCfg, Instance, Qspi, TransferConfig}; +use embassy_stm32::Config as StmCfg; +use {defmt_rtt as _, panic_probe as _}; + +const MEMORY_PAGE_SIZE: usize = 256; + +const CMD_READ: u8 = 0x03; +const CMD_HS_READ: u8 = 0x0B; +const CMD_QUAD_READ: u8 = 0x6B; + +const CMD_WRITE_PG: u8 = 0xF2; +const CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; +const CMD_READ_UUID: u8 = 0x4B; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; +const CMD_WRITE_DISABLE: u8 = 0x04; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; +const MEMORY_ADDR: u32 = 0x00000001u32; + +/// Implementation of access to flash chip. +/// Chip commands are hardcoded as it depends on used chip. +/// This implementation is using chip GD25Q64C from Giga Device +pub struct FlashMemory { + qspi: Qspi<'static, I, Blocking>, +} + +impl FlashMemory { + pub fn new(qspi: Qspi<'static, I, Blocking>) -> Self { + let mut memory = Self { qspi }; + + memory.reset_memory(); + memory.enable_quad(); + + memory + } + + fn enable_quad(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr | 0x02); + } + + fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::NONE, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_command(transaction); + } + + pub fn reset_memory(&mut self) { + self.exec_command(CMD_ENABLE_RESET); + self.exec_command(CMD_RESET); + self.wait_write_finish(); + } + + pub fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE); + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: CMD_READ_ID, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer + } + + pub fn read_uuid(&mut self) -> [u8; 16] { + let mut buffer = [0; 16]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::SING, + instruction: CMD_READ_UUID, + address: Some(0), + dummy: DummyCycles::_8, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8]) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::QUAD, + instruction: CMD_QUAD_READ, + address: Some(addr), + dummy: DummyCycles::_8, + }; + self.qspi.blocking_read(buffer, transaction); + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::NONE, + instruction: cmd, + address: Some(addr), + dummy: DummyCycles::_0, + }; + self.enable_write(); + self.qspi.blocking_command(transaction); + self.wait_write_finish(); + } + + pub fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE); + } + + pub fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K); + } + + pub fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K); + } + + pub fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE); + } + + fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::QUAD, + instruction: CMD_QUAD_WRITE_PG, + address: Some(addr), + dummy: DummyCycles::_0, + }; + self.enable_write(); + self.qspi.blocking_write(buffer, transaction); + self.wait_write_finish(); + } + + pub fn write_memory(&mut self, addr: u32, buffer: &[u8]) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size); + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_write(&buffer, transaction); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let mut config = StmCfg::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!("Embassy initialized"); + + let config = QspiCfg { + memory_size: MemorySize::_8MiB, + address_size: AddressSize::_24bit, + prescaler: 16, + cs_high_time: ChipSelectHighTime::_1Cycle, + fifo_threshold: FIFOThresholdLevel::_16Bytes, + }; + let driver = Qspi::new_blocking_bank1(p.QUADSPI, p.PD11, p.PD12, p.PE2, p.PD13, p.PB2, p.PB10, config); + let mut flash = FlashMemory::new(driver); + let flash_id = flash.read_id(); + info!("FLASH ID: {:?}", flash_id); + let mut wr_buf = [0u8; 256]; + for i in 0..256 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 256]; + flash.erase_sector(MEMORY_ADDR); + flash.write_memory(MEMORY_ADDR, &wr_buf); + flash.read_memory(MEMORY_ADDR, &mut rd_buf); + info!("WRITE BUF: {:?}", wr_buf); + info!("READ BUF: {:?}", rd_buf); + info!("End of Program, proceed to empty endless loop"); + loop {} +} diff --git a/examples/stm32h755cm4/Cargo.toml b/examples/stm32h755cm4/Cargo.toml index 7a42fbdaa..71bd50d60 100644 --- a/examples/stm32h755cm4/Cargo.toml +++ b/examples/stm32h755cm4/Cargo.toml @@ -6,28 +6,27 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32h755zi-cm4 to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm4", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm4", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } -rand_core = "0.6.3" critical-section = "1.1" micromath = "2.0.0" stm32-fmc = "0.3.0" diff --git a/examples/stm32h755cm4/src/bin/blinky.rs b/examples/stm32h755cm4/src/bin/blinky.rs index b5c547839..39112c1f5 100644 --- a/examples/stm32h755cm4/src/bin/blinky.rs +++ b/examples/stm32h755cm4/src/bin/blinky.rs @@ -10,7 +10,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".ram_d3.shared_data"] +#[unsafe(link_section = ".ram_d3.shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32h755cm7/Cargo.toml b/examples/stm32h755cm7/Cargo.toml index 4f0f69c3f..8e960932a 100644 --- a/examples/stm32h755cm7/Cargo.toml +++ b/examples/stm32h755cm7/Cargo.toml @@ -6,28 +6,27 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32h743bi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm7", "time-driver-tim3", "exti", "memory-x", "unstable-pac", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm7", "time-driver-tim3", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } -rand_core = "0.6.3" critical-section = "1.1" micromath = "2.0.0" stm32-fmc = "0.3.0" diff --git a/examples/stm32h755cm7/src/bin/blinky.rs b/examples/stm32h755cm7/src/bin/blinky.rs index 94d2226c0..b30bf4de8 100644 --- a/examples/stm32h755cm7/src/bin/blinky.rs +++ b/examples/stm32h755cm7/src/bin/blinky.rs @@ -10,7 +10,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".ram_d3.shared_data"] +#[unsafe(link_section = ".ram_d3.shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32h7b0/.cargo/config.toml b/examples/stm32h7b0/.cargo/config.toml new file mode 100644 index 000000000..870849a27 --- /dev/null +++ b/examples/stm32h7b0/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H7B0VBTx' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h7b0/Cargo.toml b/examples/stm32h7b0/Cargo.toml new file mode 100644 index 000000000..72f86e0cf --- /dev/null +++ b/examples/stm32h7b0/Cargo.toml @@ -0,0 +1,73 @@ +[package] +edition = "2021" +name = "embassy-stm32h7b0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7b0vb", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = "0.8.0" +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h7b0/build.rs b/examples/stm32h7b0/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h7b0/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/stm32h7b0/memory.x b/examples/stm32h7b0/memory.x new file mode 100644 index 000000000..6eb1bb7c1 --- /dev/null +++ b/examples/stm32h7b0/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K /* BANK_1 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* SRAM */ +} \ No newline at end of file diff --git a/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs new file mode 100644 index 000000000..dffb740a9 --- /dev/null +++ b/examples/stm32h7b0/src/bin/ospi_memory_mapped.rs @@ -0,0 +1,433 @@ +#![no_main] +#![no_std] + +// Tested on weact stm32h7b0 board + w25q64 spi flash + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::ospi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Instance, MemorySize, MemoryType, Ospi, + OspiWidth, TransferConfig, WrapSize, +}; +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) { + // RCC config + let mut config = Config::default(); + info!("START"); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + // Needed for USB + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + // External oscillator 25MHZ + config.rcc.hse = Some(Hse { + freq: Hertz(25_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV5, + mul: PllMul::MUL112, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: Some(PllDiv::DIV2), + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV2; + config.rcc.apb4_pre = APBPrescaler::DIV2; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let qspi_config = embassy_stm32::ospi::Config { + fifo_threshold: FIFOThresholdLevel::_16Bytes, + memory_type: MemoryType::Micron, + device_size: MemorySize::_8MiB, + chip_select_high_time: ChipSelectHighTime::_1Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 4, + sample_shifting: true, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: true, + max_transfer: 0, + refresh: 0, + }; + let ospi = embassy_stm32::ospi::Ospi::new_blocking_quadspi( + p.OCTOSPI1, + p.PB2, + p.PD11, + p.PD12, + p.PE2, + p.PD13, + p.PB6, + qspi_config, + ); + + let mut flash = FlashMemory::new(ospi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + let mut wr_buf = [0u8; 8]; + for i in 0..8 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 8]; + flash.erase_sector(0).await; + flash.write_memory(0, &wr_buf, true).await; + flash.read_memory(0, &mut rd_buf, true); + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + info!("READ BUF: {=[u8]:#X}", rd_buf); + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0x90000000 as *const u32) }; + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0x90000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + flash.disable_mm().await; + + info!("DONE"); + // Output pin PE3 + let mut led = Output::new(p.PE3, Level::Low, Speed::Low); + + loop { + led.toggle(); + Timer::after_millis(1000).await; + } +} + +const MEMORY_PAGE_SIZE: usize = 8; + +const CMD_QUAD_READ: u8 = 0x6B; + +const CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; + +/// Implementation of access to flash chip. +/// Chip commands are hardcoded as it depends on used chip. +/// This implementation is using chip GD25Q64C from Giga Device +pub struct FlashMemory { + ospi: Ospi<'static, I, Blocking>, +} + +impl FlashMemory { + pub async fn new(ospi: Ospi<'static, I, Blocking>) -> Self { + let mut memory = Self { ospi }; + + memory.reset_memory().await; + memory.enable_quad(); + memory + } + + async fn qpi_mode(&mut self) { + // Enter qpi mode + self.exec_command(0x38).await; + + // Set read param + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + dwidth: OspiWidth::QUAD, + instruction: Some(0xC0), + ..Default::default() + }; + self.enable_write().await; + self.ospi.blocking_write(&[0x30_u8], transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn disable_mm(&mut self) { + self.ospi.disable_memory_mapped_mode(); + } + + pub async fn enable_mm(&mut self) { + self.qpi_mode().await; + + let read_config = TransferConfig { + iwidth: OspiWidth::QUAD, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::QUAD, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x0B), // Fast read in QPI mode + dummy: DummyCycles::_8, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(0x32), // Write config + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + fn enable_quad(&mut self) { + let cr = self.read_cr(); + // info!("Read cr: {:x}", cr); + self.write_cr(cr | 0x02); + // info!("Read cr after writing: {:x}", cr); + } + + pub fn disable_quad(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr & (!(0x02))); + } + + async fn exec_command_4(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::QUAD, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_command(&transaction).unwrap(); + } + + async fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + // info!("Excuting command: {:x}", transaction.instruction); + self.ospi.blocking_command(&transaction).unwrap(); + } + + pub async fn reset_memory(&mut self) { + self.exec_command_4(CMD_ENABLE_RESET).await; + self.exec_command_4(CMD_RESET).await; + self.exec_command(CMD_ENABLE_RESET).await; + self.exec_command(CMD_RESET).await; + self.wait_write_finish(); + } + + pub async fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + // info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_id_4(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + info!("Reading id: 0x{:X}", transaction.instruction); + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_READ as u32), + address: Some(addr), + dummy: DummyCycles::_8, + ..Default::default() + }; + if use_dma { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_read(buffer, transaction).unwrap(); + } + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + async fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::NONE, + instruction: Some(cmd as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + self.ospi.blocking_command(&transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE).await; + } + + pub async fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K).await; + } + + pub async fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K).await; + } + + pub async fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE).await; + } + + async fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: OspiWidth::SING, + adsize: AddressSize::_24bit, + adwidth: OspiWidth::SING, + dwidth: OspiWidth::QUAD, + instruction: Some(CMD_QUAD_WRITE_PG as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + if use_dma { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } else { + self.ospi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + adwidth: OspiWidth::NONE, + adsize: AddressSize::_24bit, + dwidth: OspiWidth::SING, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read w25q64 register: 0x{:x}", buffer[0]); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: OspiWidth::SING, + isize: AddressSize::_8Bit, + instruction: Some(cmd as u32), + adsize: AddressSize::_24bit, + adwidth: OspiWidth::NONE, + dwidth: OspiWidth::SING, + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.ospi.blocking_write(&buffer, transaction).unwrap(); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +} diff --git a/examples/stm32h7rs/Cargo.toml b/examples/stm32h7rs/Cargo.toml index f97dfd722..5f1ce8dfc 100644 --- a/examples/stm32h7rs/Cargo.toml +++ b/examples/stm32h7rs/Cargo.toml @@ -6,27 +6,26 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32h743bi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7s3l8", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7s3l8", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "udp", "medium-ethernet", "medium-ip", "proto-ipv4"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } -embedded-nal-async = { version = "0.7.1" } +embedded-nal-async = "0.8.0" embedded-io-async = { version = "0.6.1" } -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } -rand_core = "0.6.3" critical-section = "1.1" micromath = "2.0.0" stm32-fmc = "0.3.0" diff --git a/examples/stm32h7rs/src/bin/eth.rs b/examples/stm32h7rs/src/bin/eth.rs new file mode 100644 index 000000000..6d246bb09 --- /dev/null +++ b/examples/stm32h7rs/src/bin/eth.rs @@ -0,0 +1,118 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Ipv4Address, Ipv4Cidr, StackResources}; +use embassy_stm32::eth::{Ethernet, GenericPhy, 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 heapless::Vec; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericPhy>; + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.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.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::HIGH; + } + 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> = StaticCell::new(); + let device = Ethernet::new( + PACKETS.init(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PB6, + p.PA2, + p.PG6, + p.PA7, + p.PG4, + p.PG5, + p.PG13, + p.PG12, + p.PG11, + GenericPhy::new(0), + mac_addr, + ); + + // Have to use UDP w/ static config to fit in internal flash + // 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 RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); + + // Launch network task + unwrap!(spawner.spawn(net_task(runner))); + + // Ensure DHCP configuration is up before trying connect + //stack.wait_config_up().await; + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut rx_buffer = [0; 1024]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_buffer = [0; 1024]; + + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + let socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + loop { + // You need to start a server on the host machine, for example: `nc -lu 8000` + socket + .send_to(b"Hello, world", remote_endpoint) + .await + .expect("Buffer sent"); + Timer::after_secs(1).await; + } +} diff --git a/examples/stm32h7rs/src/bin/usb_serial.rs b/examples/stm32h7rs/src/bin/usb_serial.rs new file mode 100644 index 000000000..56a9884af --- /dev/null +++ b/examples/stm32h7rs/src/bin/usb_serial.rs @@ -0,0 +1,134 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV12, + mul: PllMul::MUL300, + divp: Some(PllDiv::DIV1), //600 MHz + divq: Some(PllDiv::DIV2), // 300 MHz + divr: Some(PllDiv::DIV2), // 300 MHz + }); + config.rcc.sys = Sysclk::PLL1_P; // 600 MHz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 300 MHz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 MHz + config.rcc.voltage_scale = VoltageScale::HIGH; + config.rcc.mux.usbphycsel = mux::Usbphycsel::HSE; + } + + 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::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PM6, p.PM5, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut 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); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32h7rs/src/bin/xspi_memory_mapped.rs b/examples/stm32h7rs/src/bin/xspi_memory_mapped.rs new file mode 100644 index 000000000..88d914180 --- /dev/null +++ b/examples/stm32h7rs/src/bin/xspi_memory_mapped.rs @@ -0,0 +1,448 @@ +#![no_main] +#![no_std] + +//! For Nucleo STM32H7S3L8 MB1737, has MX25UW25645GXDI00 +//! +//! TODO: Currently this only uses single SPI, pending flash chip documentation for octo SPI. + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::time::Hertz; +use embassy_stm32::xspi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Instance, MemorySize, MemoryType, TransferConfig, + WrapSize, Xspi, XspiWidth, +}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // RCC config + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV3, + mul: PllMul::MUL150, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 600 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 300 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.apb5_pre = APBPrescaler::DIV2; // 150 Mhz + config.rcc.voltage_scale = VoltageScale::HIGH; + } + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let spi_config = embassy_stm32::xspi::Config { + fifo_threshold: FIFOThresholdLevel::_4Bytes, + memory_type: MemoryType::Macronix, + delay_hold_quarter_cycle: true, + // memory_type: MemoryType::Micron, + // delay_hold_quarter_cycle: false, + device_size: MemorySize::_32MiB, + chip_select_high_time: ChipSelectHighTime::_2Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + // 300mhz / (4+1) = 60mhz. Unsure the limit, need to find a MX25UW25645GXDI00 datasheet. + clock_prescaler: 3, + sample_shifting: false, + chip_select_boundary: 0, + max_transfer: 0, + refresh: 0, + }; + + let mut cor = cortex_m::Peripherals::take().unwrap(); + + // Not necessary, but recommended if using XIP + cor.SCB.enable_icache(); + cor.SCB.enable_dcache(&mut cor.CPUID); + + let xspi = embassy_stm32::xspi::Xspi::new_blocking_xspi( + p.XSPI2, p.PN6, p.PN2, p.PN3, p.PN4, p.PN5, p.PN8, p.PN9, p.PN10, p.PN11, p.PN1, spi_config, + ); + + let mut flash = FlashMemory::new(xspi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + + let mut wr_buf = [0u8; 8]; + for i in 0..8 { + wr_buf[i] = 0x90 + i as u8; + } + let mut rd_buf = [0u8; 8]; + flash.erase_sector(0).await; + flash.write_memory(0, &wr_buf, true).await; + flash.read_memory(0, &mut rd_buf, true); + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + info!("READ BUF: {=[u8]:#X}", rd_buf); + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0x70000000 as *const u32) }; + assert_eq!(first_u32, 0x93929190); + info!("first_u32 {:08x}", first_u32); + + let second_u32 = unsafe { *(0x70000004 as *const u32) }; + assert_eq!(second_u32, 0x97969594); + info!("second_u32 {:08x}", first_u32); + + flash.disable_mm().await; + info!("Disabled memory mapped mode"); + + info!("DONE"); + // Output pin PE3 + let mut led = Output::new(p.PE3, Level::Low, Speed::Low); + + loop { + led.toggle(); + Timer::after_millis(1000).await; + } +} + +const MEMORY_PAGE_SIZE: usize = 8; + +const CMD_READ: u8 = 0x0B; +const _CMD_QUAD_READ: u8 = 0x6B; + +const CMD_WRITE_PG: u8 = 0x02; +const _CMD_QUAD_WRITE_PG: u8 = 0x32; + +const CMD_READ_ID: u8 = 0x9F; +const CMD_READ_ID_OCTO: u16 = 0x9F60; + +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; + +const CMD_WRITE_ENABLE: u8 = 0x06; + +const CMD_CHIP_ERASE: u8 = 0xC7; +const CMD_SECTOR_ERASE: u8 = 0x20; +const CMD_BLOCK_ERASE_32K: u8 = 0x52; +const CMD_BLOCK_ERASE_64K: u8 = 0xD8; + +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; + +const CMD_WRITE_SR: u8 = 0x01; +const CMD_WRITE_CR: u8 = 0x31; + +/// Implementation of access to flash chip. +/// +/// Chip commands are hardcoded as it depends on used chip. +/// This targets a MX25UW25645GXDI00. +pub struct FlashMemory { + xspi: Xspi<'static, I, Blocking>, +} + +impl FlashMemory { + pub async fn new(xspi: Xspi<'static, I, Blocking>) -> Self { + let mut memory = Self { xspi }; + + memory.reset_memory().await; + memory.enable_octo(); + memory + } + + async fn qpi_mode(&mut self) { + // Enter qpi mode + self.exec_command(0x38).await; + + // Set read param + let transaction = TransferConfig { + iwidth: XspiWidth::QUAD, + dwidth: XspiWidth::QUAD, + instruction: Some(0xC0), + ..Default::default() + }; + self.enable_write().await; + self.xspi.blocking_write(&[0x30_u8], transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn disable_mm(&mut self) { + self.xspi.disable_memory_mapped_mode(); + } + + pub async fn enable_mm(&mut self) { + self.qpi_mode().await; + + let read_config = TransferConfig { + iwidth: XspiWidth::SING, + isize: AddressSize::_8bit, + adwidth: XspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: XspiWidth::SING, + instruction: Some(CMD_READ as u32), + dummy: DummyCycles::_8, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: XspiWidth::SING, + isize: AddressSize::_8bit, + adwidth: XspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: XspiWidth::SING, + instruction: Some(CMD_WRITE_PG as u32), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.xspi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + fn enable_octo(&mut self) { + let cr = self.read_cr(); + // info!("Read cr: {:x}", cr); + self.write_cr(cr | 0x02); + // info!("Read cr after writing: {:x}", cr); + } + + pub fn disable_octo(&mut self) { + let cr = self.read_cr(); + self.write_cr(cr & (!(0x02))); + } + + async fn exec_command_4(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: XspiWidth::QUAD, + adwidth: XspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: XspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.xspi.blocking_command(&transaction).unwrap(); + } + + async fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: XspiWidth::SING, + adwidth: XspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: XspiWidth::NONE, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + // info!("Excuting command: {:x}", transaction.instruction); + self.xspi.blocking_command(&transaction).unwrap(); + } + + pub async fn reset_memory(&mut self) { + self.exec_command_4(CMD_ENABLE_RESET).await; + self.exec_command_4(CMD_RESET).await; + self.exec_command(CMD_ENABLE_RESET).await; + self.exec_command(CMD_RESET).await; + self.wait_write_finish(); + } + + pub async fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: XspiWidth::SING, + isize: AddressSize::_8bit, + adwidth: XspiWidth::NONE, + // adsize: AddressSize::_24bit, + dwidth: XspiWidth::SING, + instruction: Some(CMD_READ_ID as u32), + ..Default::default() + }; + // info!("Reading id: 0x{:X}", transaction.instruction); + self.xspi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_id_8(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: XspiWidth::OCTO, + isize: AddressSize::_16bit, + adwidth: XspiWidth::OCTO, + address: Some(0), + adsize: AddressSize::_32bit, + dwidth: XspiWidth::OCTO, + instruction: Some(CMD_READ_ID_OCTO as u32), + dummy: DummyCycles::_4, + ..Default::default() + }; + info!("Reading id: {:#X}", transaction.instruction); + self.xspi.blocking_read(&mut buffer, transaction).unwrap(); + buffer + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: XspiWidth::SING, + adwidth: XspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: XspiWidth::SING, + instruction: Some(CMD_READ as u32), + dummy: DummyCycles::_8, + // dwidth: XspiWidth::QUAD, + // instruction: Some(CMD_QUAD_READ as u32), + // dummy: DummyCycles::_8, + address: Some(addr), + ..Default::default() + }; + if use_dma { + self.xspi.blocking_read(buffer, transaction).unwrap(); + } else { + self.xspi.blocking_read(buffer, transaction).unwrap(); + } + } + + fn wait_write_finish(&mut self) { + while (self.read_sr() & 0x01) != 0 {} + } + + async fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: XspiWidth::SING, + adwidth: XspiWidth::SING, + adsize: AddressSize::_24bit, + dwidth: XspiWidth::NONE, + instruction: Some(cmd as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + self.xspi.blocking_command(&transaction).unwrap(); + self.wait_write_finish(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE).await; + } + + pub async fn erase_block_32k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_32K).await; + } + + pub async fn erase_block_64k(&mut self, addr: u32) { + self.perform_erase(addr, CMD_BLOCK_ERASE_64K).await; + } + + pub async fn erase_chip(&mut self) { + self.exec_command(CMD_CHIP_ERASE).await; + } + + async fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: XspiWidth::SING, + adsize: AddressSize::_24bit, + adwidth: XspiWidth::SING, + dwidth: XspiWidth::SING, + instruction: Some(CMD_WRITE_PG as u32), + // dwidth: XspiWidth::QUAD, + // instruction: Some(CMD_QUAD_WRITE_PG as u32), + address: Some(addr), + dummy: DummyCycles::_0, + ..Default::default() + }; + self.enable_write().await; + if use_dma { + self.xspi.blocking_write(buffer, transaction).unwrap(); + } else { + self.xspi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: XspiWidth::SING, + isize: AddressSize::_8bit, + adwidth: XspiWidth::NONE, + adsize: AddressSize::_24bit, + dwidth: XspiWidth::SING, + instruction: Some(cmd as u32), + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.xspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read w25q64 register: 0x{:x}", buffer[0]); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: XspiWidth::SING, + isize: AddressSize::_8bit, + instruction: Some(cmd as u32), + adsize: AddressSize::_24bit, + adwidth: XspiWidth::NONE, + dwidth: XspiWidth::SING, + address: None, + dummy: DummyCycles::_0, + ..Default::default() + }; + self.xspi.blocking_write(&buffer, transaction).unwrap(); + } + + pub fn read_sr(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + + pub fn read_cr(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn write_sr(&mut self, value: u8) { + self.write_register(CMD_WRITE_SR, value); + } + + pub fn write_cr(&mut self, value: u8) { + self.write_register(CMD_WRITE_CR, value); + } +} diff --git a/examples/stm32l0/.cargo/config.toml b/examples/stm32l0/.cargo/config.toml index b050334b2..fed9cf9ce 100644 --- a/examples/stm32l0/.cargo/config.toml +++ b/examples/stm32l0/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] # replace your chip as listed in `probe-rs chip list` -runner = "probe-rs run --chip STM32L053R8Tx" +runner = "probe-rs run --chip STM32L073RZTx" [build] target = "thumbv6m-none-eabi" diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 2577f19e0..3101cf7ce 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -6,13 +6,13 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l072cz to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32l073rz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" embedded-storage = "0.3.1" embedded-io = { version = "0.6.0" } @@ -20,7 +20,7 @@ embedded-io-async = { version = "0.6.1" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } embedded-hal = "0.2.6" static_cell = { version = "2" } diff --git a/examples/stm32l0/README.md b/examples/stm32l0/README.md new file mode 100644 index 000000000..82d222027 --- /dev/null +++ b/examples/stm32l0/README.md @@ -0,0 +1,24 @@ +# Examples for STM32L0 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L073RZ it should be `probe-rs run --chip STM32L073RZTx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L073RZ it should be `stm32l073rz`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32l0/src/bin/async-tsc.rs b/examples/stm32l0/src/bin/async-tsc.rs deleted file mode 100644 index c40b86af9..000000000 --- a/examples/stm32l0/src/bin/async-tsc.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. -// -// Suggested physical setup on STM32L073RZ Nucleo board: -// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. -// - Connect one end of a 1K resistor to pin A1 and leave the other end loose. -// The loose end will act as touch sensor which will register your touch. -// -// Troubleshooting the setup: -// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, -// now the led should light up. Next try using a different value for the sampling capacitor. -// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. -// -// All configuration values and sampling capacitor value have been determined experimentally. -// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values. -// -#![no_std] -#![no_main] - -use defmt::*; -use embassy_stm32::bind_interrupts; -use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::tsc::{self, *}; -use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; - -bind_interrupts!(struct Irqs { - TSC => InterruptHandler; -}); - -#[cortex_m_rt::exception] -unsafe fn HardFault(_: &cortex_m_rt::ExceptionFrame) -> ! { - cortex_m::peripheral::SCB::sys_reset(); -} - -/// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip. -/// -/// Make sure you check/update the following (whether you use the L073RZ or another board): -/// -/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name. -/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`. -/// * [ ] If your board has a special clock or power configuration, make sure that it is -/// set up appropriately. -/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals -/// to match your schematic -/// -/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: -/// -/// * Which example you are trying to run -/// * Which chip and board you are using -/// -/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org -#[embassy_executor::main] -async fn main(_spawner: embassy_executor::Spawner) { - let device_config = embassy_stm32::Config::default(); - let context = embassy_stm32::init(device_config); - - let config = tsc::Config { - ct_pulse_high_length: ChargeTransferPulseCycle::_4, - ct_pulse_low_length: ChargeTransferPulseCycle::_4, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(2).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_16, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: TscIOPin::Group1Io1.into(), - shield_ios: 0, // no shield - sampling_ios: TscIOPin::Group1Io2.into(), - }; - - let mut g1: PinGroup = PinGroup::new(); - g1.set_io1(context.PA0, PinType::Sample); - g1.set_io2(context.PA1, PinType::Channel); - - let mut touch_controller = tsc::Tsc::new_async( - context.TSC, - Some(g1), - None, - None, - None, - None, - None, - None, - None, - config, - Irqs, - ); - - // Check if TSC is ready - if touch_controller.get_state() != State::Ready { - info!("TSC not ready!"); - loop {} // Halt execution - } - info!("TSC initialized successfully"); - - // LED2 on the STM32L073RZ nucleo-board (PA5) - let mut led = Output::new(context.PA5, Level::High, Speed::Low); - - // smaller sample capacitor discharge faster and can be used with shorter delay. - let discharge_delay = 5; // ms - - info!("Starting touch_controller interface"); - loop { - touch_controller.start(); - touch_controller.pend_for_acquisition().await; - touch_controller.discharge_io(true); - Timer::after_millis(discharge_delay).await; - - let grp1_status = touch_controller.group_get_status(Group::One); - match grp1_status { - GroupStatus::Complete => { - let group_one_val = touch_controller.group_get_value(Group::One); - info!("{}", group_one_val); - led.set_high(); - } - GroupStatus::Ongoing => led.set_low(), - } - } -} diff --git a/examples/stm32l0/src/bin/blocking-tsc.rs b/examples/stm32l0/src/bin/blocking-tsc.rs deleted file mode 100644 index 7e4f40946..000000000 --- a/examples/stm32l0/src/bin/blocking-tsc.rs +++ /dev/null @@ -1,116 +0,0 @@ -// Example of polling TSC (Touch Sensing Controller) that lights an LED when touch is detected. -// -// Suggested physical setup on STM32L073RZ Nucleo board: -// - Connect a 1000pF capacitor between pin A0 and GND. This is your sampling capacitor. -// - Connect one end of a 1K resistor to pin A1 and leave the other end loose. -// The loose end will act as touch sensor which will register your touch. -// -// Troubleshooting the setup: -// - If no touch seems to be registered, then try to disconnect the sampling capacitor from GND momentarily, -// now the led should light up. Next try using a different value for the sampling capacitor. -// Also experiment with increasing the values for `ct_pulse_high_length`, `ct_pulse_low_length`, `pulse_generator_prescaler`, `max_count_value` and `discharge_delay`. -// -// All configuration values and sampling capacitor value have been determined experimentally. -// Suitable configuration and discharge delay values are highly dependent on the value of the sample capacitor. For example, a shorter discharge delay can be used with smaller capacitor values. -// -#![no_std] -#![no_main] - -use defmt::*; -use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::tsc::{self, *}; -use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _}; - -/// This example is written for the nucleo-stm32l073rz, with a stm32l073rz chip. -/// -/// Make sure you check/update the following (whether you use the L073RZ or another board): -/// -/// * [ ] Update .cargo/config.toml with the correct `probe-rs run --chip STM32L073RZTx`chip name. -/// * [ ] Update Cargo.toml to have the correct `embassy-stm32` feature, for L073RZ it should be `stm32l073rz`. -/// * [ ] If your board has a special clock or power configuration, make sure that it is -/// set up appropriately. -/// * [ ] If your board has different pin mapping, update any pin numbers or peripherals -/// to match your schematic -/// -/// If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: -/// -/// * Which example you are trying to run -/// * Which chip and board you are using -/// -/// Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org -#[embassy_executor::main] -async fn main(_spawner: embassy_executor::Spawner) { - let device_config = embassy_stm32::Config::default(); - let context = embassy_stm32::init(device_config); - - let tsc_conf = Config { - ct_pulse_high_length: ChargeTransferPulseCycle::_4, - ct_pulse_low_length: ChargeTransferPulseCycle::_4, - spread_spectrum: false, - spread_spectrum_deviation: SSDeviation::new(2).unwrap(), - spread_spectrum_prescaler: false, - pulse_generator_prescaler: PGPrescalerDivider::_16, - max_count_value: MaxCount::_255, - io_default_mode: false, - synchro_pin_polarity: false, - acquisition_mode: false, - max_count_interrupt: false, - channel_ios: TscIOPin::Group1Io1.into(), - shield_ios: 0, // no shield - sampling_ios: TscIOPin::Group1Io2.into(), - }; - - let mut g1: PinGroup = PinGroup::new(); - g1.set_io1(context.PA0, PinType::Sample); - g1.set_io2(context.PA1, PinType::Channel); - - let mut touch_controller = tsc::Tsc::new_blocking( - context.TSC, - Some(g1), - None, - None, - None, - None, - None, - None, - None, - tsc_conf, - ); - - // Check if TSC is ready - if touch_controller.get_state() != State::Ready { - info!("TSC not ready!"); - loop {} // Halt execution - } - info!("TSC initialized successfully"); - - // LED2 on the STM32L073RZ nucleo-board (PA5) - let mut led = Output::new(context.PA5, Level::High, Speed::Low); - - // smaller sample capacitor discharge faster and can be used with shorter delay. - let discharge_delay = 5; // ms - - // the interval at which the loop polls for new touch sensor values - let polling_interval = 100; // ms - - info!("polling for touch"); - loop { - touch_controller.start(); - touch_controller.poll_for_acquisition(); - touch_controller.discharge_io(true); - Timer::after_millis(discharge_delay).await; - - let grp1_status = touch_controller.group_get_status(Group::One); - match grp1_status { - GroupStatus::Complete => { - let group_one_val = touch_controller.group_get_value(Group::One); - info!("{}", group_one_val); - led.set_high(); - } - GroupStatus::Ongoing => led.set_low(), - } - - Timer::after_millis(polling_interval).await; - } -} diff --git a/examples/stm32l0/src/bin/eeprom.rs b/examples/stm32l0/src/bin/eeprom.rs new file mode 100644 index 000000000..370246644 --- /dev/null +++ b/examples/stm32l0/src/bin/eeprom.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::{Flash, EEPROM_BASE, EEPROM_SIZE}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + info!("Hello Eeprom! Start: {}, Size: {}", EEPROM_BASE, EEPROM_SIZE); + + const ADDR: u32 = 0x0; + + let mut f = Flash::new_blocking(p.FLASH); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.eeprom_read_slice(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.eeprom_write_slice(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.eeprom_read_slice(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); +} diff --git a/examples/stm32l0/src/bin/tsc_async.rs b/examples/stm32l0/src/bin/tsc_async.rs new file mode 100644 index 000000000..dae351c2e --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_async.rs @@ -0,0 +1,116 @@ +// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: +// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as the channel pin, TSC group 1 IO2 (label A1) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 25). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let mut pin_group: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group.set_io1::(context.PA0); + let sensor = pin_group.set_io2::(context.PA1); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(pin_group.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + info!("TSC not ready!"); + return; + } + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::Low, Speed::Low); + + let discharge_delay = 5; // ms + + info!("Starting touch_controller interface"); + loop { + touch_controller.set_active_channels_mask(sensor.pin.into()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + let group_val = touch_controller.group_get_value(sensor.pin.group()); + info!("Touch value: {}", group_val); + + if group_val < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + + Timer::after_millis(100).await; + } +} diff --git a/examples/stm32l0/src/bin/tsc_blocking.rs b/examples/stm32l0/src/bin/tsc_blocking.rs new file mode 100644 index 000000000..e1f24639b --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_blocking.rs @@ -0,0 +1,142 @@ +// Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PA1 and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 1 of the TSC on the STM32L073RZ Nucleo board: +// - PA0 as the sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as the channel pin, TSC group 1 IO2 (label A1) +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED is turned on when touch is detected (sensor value < 25). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io1::(context.PA0); + let tsc_sensor = g1.set_io2::(context.PA1); + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: tsc::IOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/examples/stm32l0/src/bin/tsc_multipin.rs b/examples/stm32l0/src/bin/tsc_multipin.rs new file mode 100644 index 000000000..6343de141 --- /dev/null +++ b/examples/stm32l0/src/bin/tsc_multipin.rs @@ -0,0 +1,209 @@ +// Example of TSC (Touch Sensing Controller) using multiple pins from the same tsc-group. +// +// What is special about using multiple TSC pins as sensor channels from the same TSC group, +// is that only one TSC pin for each TSC group can be acquired and read at the time. +// To control which channel pins are acquired and read, we must write a mask before initiating an +// acquisition. To help manage and abstract all this business away, we can organize our channel +// pins into acquisition banks. Each acquisition bank can contain exactly one channel pin per TSC +// group and it will contain the relevant mask. +// +// This example demonstrates how to: +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels_bank method to switch between sets of different channels (acquisition banks) +// 3. Read and interpret touch values from multiple channels in the same group +// +// Suggested physical setup on STM32L073RZ Nucleo board: +// - Connect a 1000pF capacitor between pin PA0 (label A0) and GND. This is the sampling capacitor for TSC +// group 1. +// - Connect one end of a 1K resistor to pin PA1 (label A1) and leave the other end loose. +// The loose end will act as a touch sensor. +// +// - Connect a 1000pF capacitor between pin PB3 (label D3) and GND. This is the sampling capacitor for TSC +// group 5. +// - Connect one end of another 1K resistor to pin PB4 and leave the other end loose. +// The loose end will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PB6 and leave the other end loose. +// The loose end will act as a touch sensor. +// +// The example uses pins from two TSC groups. +// - PA0 as sampling capacitor, TSC group 1 IO1 (label A0) +// - PA1 as channel, TSC group 1 IO2 (label A1) +// - PB3 as sampling capacitor, TSC group 5 IO1 (label D3) +// - PB4 as channel, TSC group 5 IO2 (label D10) +// - PB6 as channel, TSC group 5 IO3 (label D5) +// +// The pins have been chosen to make it easy to simply add capacitors directly onto the board and +// connect one leg to GND, and to easily add resistors to the board with no special connectors, +// breadboards, special wires or soldering required. All you need is the capacitors and resistors. +// +// The program reads the designated channel pins and adjusts the LED blinking +// pattern based on which sensor(s) are touched: +// - No touch: LED off +// - one sensor touched: Slow blinking +// - two sensors touched: Fast blinking +// - three sensors touched: LED constantly on +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. +// Pins have been chosen for their convenient locations on the STM32L073RZ board. Refer to the +// official relevant STM32 datasheets and nucleo-board user manuals to find suitable +// alternative pins. +// +// Beware for STM32L073RZ nucleo-board, that PA2 and PA3 is used for the uart connection to +// the programmer chip. If you try to use these two pins for TSC, you will get strange +// readings, unless you somehow reconfigure/re-wire your nucleo-board. +// No errors or warnings will be emitted, they will just silently not work as expected. +// (see nucleo user manual UM1724, Rev 14, page 25) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +const SENSOR_THRESHOLD: u16 = 35; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &AcquisitionBank, +) { + touch_controller.set_active_channels_bank(tsc_acquisition_bank); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + let discharge_delay = 5; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + let mut pin_group1: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group1.set_io1::(context.PA0); + let tsc_sensor0 = pin_group1.set_io2(context.PA1); + + let mut pin_group5: PinGroupWithRoles = PinGroupWithRoles::default(); + pin_group5.set_io1::(context.PB3); + let tsc_sensor1 = pin_group5.set_io2(context.PB4); + let tsc_sensor2 = pin_group5.set_io3(context.PB6); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_16, + ct_pulse_low_length: ChargeTransferPulseCycle::_16, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(pin_group1.pin_group), + g5: Some(pin_group5.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 in this example belong to different TSC-groups, + // therefore we can acquire and read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g1_pin: Some(tsc_sensor0), + g5_pin: Some(tsc_sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. Therefore, we organize them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g5_pin: Some(tsc_sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + // LED2 on the STM32L073RZ nucleo-board (PA5) + let mut led = Output::new(context.PA5, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1 = touch_controller.get_acquisition_bank_values(&bank1); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2 = touch_controller.get_acquisition_bank_values(&bank2); + + let mut touched_sensors_count = 0; + for reading in readings1.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + for reading in readings2.iter() { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/examples/stm32l0/src/bin/usart_irq.rs b/examples/stm32l0/src/bin/usart_irq.rs index 2c96a8bc2..a51ddbcbb 100644 --- a/examples/stm32l0/src/bin/usart_irq.rs +++ b/examples/stm32l0/src/bin/usart_irq.rs @@ -21,7 +21,7 @@ async fn main(_spawner: Spawner) { config.baudrate = 9600; let mut tx_buf = [0u8; 256]; let mut rx_buf = [0u8; 256]; - let mut usart = BufferedUart::new(p.USART2, Irqs, p.PA3, p.PA2, &mut tx_buf, &mut rx_buf, config).unwrap(); + let mut usart = BufferedUart::new(p.USART2, p.PA3, p.PA2, &mut tx_buf, &mut rx_buf, Irqs, config).unwrap(); usart.write_all(b"Hello Embassy World!\r\n").await.unwrap(); info!("wrote Hello, starting echo"); diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index 062044f32..a0a7916a7 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -5,20 +5,20 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } embedded-storage = "0.3.1" diff --git a/examples/stm32l1/src/bin/eeprom.rs b/examples/stm32l1/src/bin/eeprom.rs new file mode 100644 index 000000000..370246644 --- /dev/null +++ b/examples/stm32l1/src/bin/eeprom.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::{Flash, EEPROM_BASE, EEPROM_SIZE}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + info!("Hello Eeprom! Start: {}, Size: {}", EEPROM_BASE, EEPROM_SIZE); + + const ADDR: u32 = 0x0; + + let mut f = Flash::new_blocking(p.FLASH); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.eeprom_read_slice(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!(f.eeprom_write_slice(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + + info!("Reading..."); + let mut buf = [0u8; 8]; + unwrap!(f.eeprom_read_slice(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); +} diff --git a/examples/stm32l1/src/bin/usart.rs b/examples/stm32l1/src/bin/usart.rs new file mode 100644 index 000000000..dba79b8b4 --- /dev/null +++ b/examples/stm32l1/src/bin/usart.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USART2 => usart::InterruptHandler; +}); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new_blocking(p.USART2, p.PA3, p.PA2, config).unwrap(); + let desired_baudrate = 9600; // Default is 115200 and 9600 is used as example + + match usart.set_baudrate(desired_baudrate) { + Ok(_) => info!("Baud rate set to {}", desired_baudrate), + Err(err) => error!("Error setting baudrate to {}: {}", desired_baudrate, err), + } + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} diff --git a/examples/stm32l1/src/bin/usb_serial.rs b/examples/stm32l1/src/bin/usb_serial.rs index 837f7fa57..a35f1d7a7 100644 --- a/examples/stm32l1/src/bin/usb_serial.rs +++ b/examples/stm32l1/src/bin/usb_serial.rs @@ -41,11 +41,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-Serial Example"); config.serial_number = Some("123456"); - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 64]; diff --git a/examples/stm32l4/.cargo/config.toml b/examples/stm32l4/.cargo/config.toml index 83fc6d6f8..d71fb1517 100644 --- a/examples/stm32l4/.cargo/config.toml +++ b/examples/stm32l4/.cargo/config.toml @@ -2,7 +2,8 @@ # replace STM32F429ZITx with your chip as listed in `probe-rs chip list` #runner = "probe-rs run --chip STM32L475VGT6" #runner = "probe-rs run --chip STM32L475VG" -runner = "probe-rs run --chip STM32L4S5QI" +#runner = "probe-rs run --chip STM32L4S5QI" +runner = "probe-rs run --chip STM32L4R5ZITxP" [build] target = "thumbv7em-none-eabi" diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index c5478b17b..7da09e5b0 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -6,20 +6,20 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l4s5vi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net-adin1110 = { version = "0.2.0", path = "../../embassy-net-adin1110" } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4r5zi", "memory-x", "time-driver-any", "exti", "chrono", "dual-bank"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal" } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net-adin1110 = { version = "0.3.0", path = "../../embassy-net-adin1110" } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "udp", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } embedded-io = { version = "0.6.0", features = ["defmt-03"] } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" @@ -27,10 +27,9 @@ embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-bus = { version = "0.1", features = ["async"] } -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } chrono = { version = "^0.4", default-features = false } -rand = { version = "0.8.5", default-features = false } static_cell = "2" micromath = "2.0.0" diff --git a/examples/stm32l4/README.md b/examples/stm32l4/README.md new file mode 100644 index 000000000..e463c18a0 --- /dev/null +++ b/examples/stm32l4/README.md @@ -0,0 +1,24 @@ +# Examples for STM32L4 family +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L4R5ZI-P it should be `probe-rs run --chip STM32L4R5ZITxP`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L4R5ZI-P it should be `stm32l4r5zi`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32l4/src/bin/dac.rs b/examples/stm32l4/src/bin/dac.rs index fdbf1d374..50db0e082 100644 --- a/examples/stm32l4/src/bin/dac.rs +++ b/examples/stm32l4/src/bin/dac.rs @@ -3,7 +3,6 @@ use defmt::*; use embassy_stm32::dac::{DacCh1, Value}; -use embassy_stm32::dma::NoDma; use {defmt_rtt as _, panic_probe as _}; #[cortex_m_rt::entry] @@ -11,7 +10,7 @@ fn main() -> ! { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + let mut dac = DacCh1::new_blocking(p.DAC1, p.PA4); loop { for v in 0..=255 { diff --git a/examples/stm32l4/src/bin/dac_dma.rs b/examples/stm32l4/src/bin/dac_dma.rs index d01b016c0..cde24f411 100644 --- a/examples/stm32l4/src/bin/dac_dma.rs +++ b/examples/stm32l4/src/bin/dac_dma.rs @@ -4,11 +4,13 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dac::{DacCh1, DacCh2, ValueArray}; +use embassy_stm32::mode::Async; use embassy_stm32::pac::timer::vals::Mms; -use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; +use embassy_stm32::peripherals::{DAC1, TIM6, TIM7}; use embassy_stm32::rcc::frequency; use embassy_stm32::time::Hertz; use embassy_stm32::timer::low_level::Timer; +use embassy_stm32::Peri; use micromath::F32Ext; use {defmt_rtt as _, panic_probe as _}; @@ -27,7 +29,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn dac_task1(tim: TIM6, mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { +async fn dac_task1(tim: Peri<'static, TIM6>, mut dac: DacCh1<'static, DAC1, Async>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM6 frequency is {}", frequency::()); @@ -70,7 +72,7 @@ async fn dac_task1(tim: TIM6, mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { } #[embassy_executor::task] -async fn dac_task2(tim: TIM7, mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { +async fn dac_task2(tim: Peri<'static, TIM7>, mut dac: DacCh2<'static, DAC1, Async>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM7 frequency is {}", frequency::()); diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs index 8f23e4083..dc90a3b85 100644 --- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -38,7 +38,6 @@ use embedded_io::Write as bWrite; use embedded_io_async::Write; use heapless::Vec; use panic_probe as _; -use rand::RngCore; use static_cell::StaticCell; bind_interrupts!(struct Irqs { @@ -51,7 +50,7 @@ bind_interrupts!(struct Irqs { // MAC-address used by the adin1110 const MAC: [u8; 6] = [0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff]; // Static IP settings -const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address([192, 168, 1, 5]), 24); +const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address::new(192, 168, 1, 5), 24); // Listen port for the webserver const HTTP_LISTEN_PORT: u16 = 80; @@ -206,12 +205,11 @@ async fn main(spawner: Spawner) { }; // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); let cfg = wait_for_config(stack).await; let local_addr = cfg.address.address(); @@ -274,7 +272,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config; @@ -323,8 +321,8 @@ async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } // same panicking *behavior* as `panic-probe` but doesn't print a panic message diff --git a/examples/stm32l4/src/bin/tsc_async.rs b/examples/stm32l4/src/bin/tsc_async.rs new file mode 100644 index 000000000..b9a059e2e --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_async.rs @@ -0,0 +1,108 @@ +// Example of async TSC (Touch Sensing Controller) that lights an LED when touch is detected. +// +// This example demonstrates: +// 1. Configuring a single TSC channel pin +// 2. Using the async TSC interface +// 3. Waiting for acquisition completion using `pend_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// Suggested physical setup on STM32L4R5ZI-P board: +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// The example uses two pins from Group 2 of the TSC: +// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 +// - PB5 (D21) as the channel pin, TSC group 2 IO2 +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `pend_for_acquisition`, and reads the value. +// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). +// - Touch values are logged to the console. +// +// Troubleshooting: +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value. +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let mut pin_group: PinGroupWithRoles = PinGroupWithRoles::default(); + // D25 + pin_group.set_io1::(context.PB4); + // D21 + let tsc_sensor = pin_group.set_io2::(context.PB5); + + let pin_groups: PinGroups = PinGroups { + g2: Some(pin_group.pin_group), + ..Default::default() + }; + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, tsc_conf, Irqs).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + info!("TSC not ready!"); + return; + } + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + let discharge_delay = 1; // ms + + info!("Starting touch_controller interface"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + let group_val = touch_controller.group_get_value(tsc_sensor.pin.group()); + info!("Touch value: {}", group_val); + + if group_val < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + + Timer::after_millis(100).await; + } +} diff --git a/examples/stm32l4/src/bin/tsc_blocking.rs b/examples/stm32l4/src/bin/tsc_blocking.rs new file mode 100644 index 000000000..12084f8e2 --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_blocking.rs @@ -0,0 +1,147 @@ +// # Example of blocking TSC (Touch Sensing Controller) that lights an LED when touch is detected +// +// This example demonstrates how to use the Touch Sensing Controller (TSC) in blocking mode on an STM32L4R5ZI-P board. +// +// ## This example demonstrates: +// +// 1. Configuring a single TSC channel pin +// 2. Using the blocking TSC interface with polling +// 3. Waiting for acquisition completion using `poll_for_acquisition` +// 4. Reading touch values and controlling an LED based on the results +// +// ## Suggested physical setup on STM32L4R5ZI-P board: +// +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is your sampling capacitor. +// - Connect one end of a 1K resistor to pin PB5 (D21) and leave the other end loose. +// The loose end will act as the touch sensor which will register your touch. +// +// ## Pin Configuration: +// +// The example uses two pins from Group 2 of the TSC: +// - PB4 (D25) as the sampling capacitor, TSC group 2 IO1 +// - PB5 (D21) as the channel pin, TSC group 2 IO2 +// +// ## Program Behavior: +// +// The program continuously reads the touch sensor value: +// - It starts acquisition, waits for completion using `poll_for_acquisition`, and reads the value. +// - The LED (connected to PB14) is turned on when touch is detected (sensor value < SENSOR_THRESHOLD). +// - Touch values are logged to the console. +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 25). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, +// pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards, there might be overlapping concerns between some pins, +// such as UART connections for the programmer. No errors or warnings will be emitted if you +// try to use such a pin for TSC, but you may get strange sensor readings. +// +// Note: Configuration values and sampling capacitor value have been determined experimentally. +// Optimal values may vary based on your specific hardware setup. Refer to the official +// STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const SENSOR_THRESHOLD: u16 = 25; // Adjust this value based on your setup + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + let tsc_conf = Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_4, + ct_pulse_low_length: ChargeTransferPulseCycle::_4, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + // D25 + g2.set_io1::(context.PB4); + // D21 + let tsc_sensor = g2.set_io2::(context.PB5); + + let pin_groups: PinGroups = PinGroups { + g2: Some(g2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_blocking(context.TSC, pin_groups, tsc_conf).unwrap(); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + // smaller sample capacitor discharge faster and can be used with shorter delay. + let discharge_delay = 5; // ms + + // the interval at which the loop polls for new touch sensor values + let polling_interval = 100; // ms + + info!("polling for touch"); + loop { + touch_controller.set_active_channels_mask(tsc_sensor.pin.into()); + touch_controller.start(); + touch_controller.poll_for_acquisition(); + touch_controller.discharge_io(true); + Timer::after_millis(discharge_delay).await; + + match read_touch_value(&mut touch_controller, tsc_sensor.pin).await { + Some(v) => { + info!("sensor value {}", v); + if v < SENSOR_THRESHOLD { + led.set_high(); + } else { + led.set_low(); + } + } + None => led.set_low(), + } + + Timer::after_millis(polling_interval).await; + } +} + +const MAX_GROUP_STATUS_READ_ATTEMPTS: usize = 10; + +// attempt to read group status and delay when still ongoing +async fn read_touch_value( + touch_controller: &mut tsc::Tsc<'_, peripherals::TSC, mode::Blocking>, + sensor_pin: tsc::IOPin, +) -> Option { + for _ in 0..MAX_GROUP_STATUS_READ_ATTEMPTS { + match touch_controller.group_get_status(sensor_pin.group()) { + GroupStatus::Complete => { + return Some(touch_controller.group_get_value(sensor_pin.group())); + } + GroupStatus::Ongoing => { + // if you end up here a lot, then you prob need to increase discharge_delay + // or consider changing the code to adjust the discharge_delay dynamically + info!("Acquisition still ongoing"); + Timer::after_millis(1).await; + } + } + } + None +} diff --git a/examples/stm32l4/src/bin/tsc_multipin.rs b/examples/stm32l4/src/bin/tsc_multipin.rs new file mode 100644 index 000000000..8fec5ddc4 --- /dev/null +++ b/examples/stm32l4/src/bin/tsc_multipin.rs @@ -0,0 +1,198 @@ +// # Example of TSC (Touch Sensing Controller) using multiple pins from the same TSC group +// +// This example demonstrates how to use the Touch Sensing Controller (TSC) with multiple pins, including pins from the same TSC group, on an STM32L4R5ZI-P board. +// +// ## Key Concepts +// +// - Only one TSC pin for each TSC group can be acquired and read at a time. +// - To control which channel pins are acquired and read, we must write a mask before initiating an acquisition. +// - We organize channel pins into acquisition banks to manage this process efficiently. +// - Each acquisition bank can contain exactly one channel pin per TSC group and will contain the relevant mask. +// +// ## This example demonstrates how to: +// +// 1. Configure multiple channel pins within a single TSC group +// 2. Use the set_active_channels_bank method to switch between sets of different channels (acquisition banks) +// 3. Read and interpret touch values from multiple channels in the same group +// +// ## Suggested physical setup on STM32L4R5ZI-P board: +// +// - Connect a 1000pF capacitor between pin PB12 (D19) and GND. This is the sampling capacitor for TSC group 1. +// - Connect one end of a 1K resistor to pin PB13 (D18) and leave the other end loose. This will act as a touch sensor. +// - Connect a 1000pF capacitor between pin PB4 (D25) and GND. This is the sampling capacitor for TSC group 2. +// - Connect one end of a 1K resistor to pin PB5 (D22) and leave the other end loose. This will act as a touch sensor. +// - Connect one end of another 1K resistor to pin PB6 (D71) and leave the other end loose. This will act as a touch sensor. +// +// ## Pin Configuration: +// +// The example uses pins from two TSC groups: +// +// - Group 1: +// - PB12 (D19) as sampling capacitor (TSC group 1 IO1) +// - PB13 (D18) as channel (TSC group 1 IO2) +// - Group 2: +// - PB4 (D25) as sampling capacitor (TSC group 2 IO1) +// - PB5 (D22) as channel (TSC group 2 IO2) +// - PB6 (D71) as channel (TSC group 2 IO3) +// +// The pins have been chosen for their convenient locations on the STM32L4R5ZI-P board, making it easy to add capacitors and resistors directly to the board without special connectors, breadboards, or soldering. +// +// ## Program Behavior: +// +// The program reads the designated channel pins and adjusts the LED (connected to PB14) blinking pattern based on which sensor(s) are touched: +// +// - No touch: LED off +// - One sensor touched: Slow blinking +// - Two sensors touched: Fast blinking +// - Three sensors touched: LED constantly on +// +// ## Troubleshooting: +// +// - If touch is not detected, try adjusting the SENSOR_THRESHOLD value (currently set to 20). +// - Experiment with different values for ct_pulse_high_length, ct_pulse_low_length, pulse_generator_prescaler, max_count_value, and discharge_delay to optimize sensitivity. +// - Be aware that for some boards there will be overlapping concerns between some pins, for +// example UART connection for the programmer to the MCU and a TSC pin. No errors or warning will +// be emitted if you try to use such a pin for TSC, but you will get strange sensor readings. +// +// Note: Configuration values and sampling capacitor values have been determined experimentally. Optimal values may vary based on your specific hardware setup. Refer to the official STM32L4R5ZI-P datasheet and user manuals for more information on pin configurations and TSC functionality. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, mode, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + TSC => InterruptHandler; +}); + +const SENSOR_THRESHOLD: u16 = 20; + +async fn acquire_sensors( + touch_controller: &mut Tsc<'static, peripherals::TSC, mode::Async>, + tsc_acquisition_bank: &AcquisitionBank, +) { + touch_controller.set_active_channels_bank(tsc_acquisition_bank); + touch_controller.start(); + touch_controller.pend_for_acquisition().await; + touch_controller.discharge_io(true); + let discharge_delay = 1; // ms + Timer::after_millis(discharge_delay).await; +} + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let device_config = embassy_stm32::Config::default(); + let context = embassy_stm32::init(device_config); + + // ---------- initial configuration of TSC ---------- + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io1::(context.PB12); + let sensor0 = g1.set_io2::(context.PB13); + + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + g2.set_io1::(context.PB4); + let sensor1 = g2.set_io2(context.PB5); + let sensor2 = g2.set_io3(context.PB6); + + let config = tsc::Config { + ct_pulse_high_length: ChargeTransferPulseCycle::_16, + ct_pulse_low_length: ChargeTransferPulseCycle::_16, + spread_spectrum: false, + spread_spectrum_deviation: SSDeviation::new(2).unwrap(), + spread_spectrum_prescaler: false, + pulse_generator_prescaler: PGPrescalerDivider::_16, + max_count_value: MaxCount::_255, + io_default_mode: false, + synchro_pin_polarity: false, + acquisition_mode: false, + max_count_interrupt: false, + }; + + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + g2: Some(g2.pin_group), + ..Default::default() + }; + + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); + + // ---------- setting up acquisition banks ---------- + // sensor0 and sensor1 belong to different TSC-groups, therefore we can acquire and + // read them both in one go. + let bank1 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g1_pin: Some(sensor0), + g2_pin: Some(sensor1), + ..Default::default() + }); + // `sensor1` and `sensor2` belongs to the same TSC-group, therefore we must make sure to + // acquire them one at the time. We do this by organizing them into different acquisition banks. + let bank2 = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g2_pin: Some(sensor2), + ..Default::default() + }); + + // Check if TSC is ready + if touch_controller.get_state() != State::Ready { + crate::panic!("TSC not ready!"); + } + + info!("TSC initialized successfully"); + + let mut led = Output::new(context.PB14, Level::High, Speed::Low); + + let mut led_state = false; + + loop { + acquire_sensors(&mut touch_controller, &bank1).await; + let readings1 = touch_controller.get_acquisition_bank_values(&bank1); + acquire_sensors(&mut touch_controller, &bank2).await; + let readings2 = touch_controller.get_acquisition_bank_values(&bank2); + + let mut touched_sensors_count = 0; + for reading in readings1.iter().chain(readings2.iter()) { + info!("{}", reading); + if reading.sensor_value < SENSOR_THRESHOLD { + touched_sensors_count += 1; + } + } + + match touched_sensors_count { + 0 => { + // No sensors touched, turn off the LED + led.set_low(); + led_state = false; + } + 1 => { + // One sensor touched, blink slowly + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(200).await; + } + 2 => { + // Two sensors touched, blink faster + led_state = !led_state; + if led_state { + led.set_high(); + } else { + led.set_low(); + } + Timer::after_millis(50).await; + } + 3 => { + // All three sensors touched, LED constantly on + led.set_high(); + led_state = true; + } + _ => crate::unreachable!(), // This case should never occur with 3 sensors + } + } +} diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs index c3b1211d8..af90e297e 100644 --- a/examples/stm32l4/src/bin/usb_serial.rs +++ b/examples/stm32l4/src/bin/usb_serial.rs @@ -62,13 +62,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-serial example"); config.serial_number = Some("12345678"); - // Required for windows compatibility. - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/stm32l432/.cargo/config.toml b/examples/stm32l432/.cargo/config.toml new file mode 100644 index 000000000..0a42c584b --- /dev/null +++ b/examples/stm32l432/.cargo/config.toml @@ -0,0 +1,13 @@ +[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 STM32L475VGT6" +#runner = "probe-rs run --chip STM32L475VG" +#runner = "probe-rs run --chip STM32L4S5QI" +runner = "probe-rs run --chip STM32L432KCUx --connect-under-reset --speed 3300" + + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32l432/Cargo.toml b/examples/stm32l432/Cargo.toml new file mode 100644 index 000000000..c38462355 --- /dev/null +++ b/examples/stm32l432/Cargo.toml @@ -0,0 +1,30 @@ +[package] +edition = "2021" +name = "embassy-stm32l4-examples" +version = "0.1.1" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32l4s5vi to your chip name, if necessary. +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l432kc", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = [ "defmt" ] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = [ "arch-cortex-m", "executor-thread", "defmt" ] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime", "tick-hz-32_768" ] } +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-bus = { version = "0.1", features = ["async"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } + +[profile.release] +debug = 2 + +[[bin]] +name = "qspi_mmap" +path = "src/bin/qspi_mmap.rs" +test = false diff --git a/examples/stm32l432/README.md b/examples/stm32l432/README.md new file mode 100644 index 000000000..3dac97f03 --- /dev/null +++ b/examples/stm32l432/README.md @@ -0,0 +1,30 @@ + +# Examples for STM32L432 family + +Examples in this repo should work with [NUCLEO-L432KC](https://www.st.com/en/evaluation-tools/nucleo-l432kc.html) board. + +Run individual examples with +``` +cargo run --bin +``` +for example +``` +cargo run --bin blinky +``` + + + +## Checklist before running examples +You might need to adjust `.cargo/config.toml`, `Cargo.toml` and possibly update pin numbers or peripherals to match the specific MCU or board you are using. + +* [ ] Update .cargo/config.toml with the correct probe-rs command to use your specific MCU. For example for L432KCU6 it should be `probe-rs run --chip STM32L432KCUx`. (use `probe-rs chip list` to find your chip) +* [ ] Update Cargo.toml to have the correct `embassy-stm32` feature. For example for L432KCU6 it should be `stm32l432kc`. Look in the `Cargo.toml` file of the `embassy-stm32` project to find the correct feature flag for your chip. +* [ ] If your board has a special clock or power configuration, make sure that it is set up appropriately. +* [ ] If your board has different pin mapping, update any pin numbers or peripherals in the given example code to match your schematic + +If you are unsure, please drop by the Embassy Matrix chat for support, and let us know: + +* Which example you are trying to run +* Which chip and board you are using + +Embassy Chat: https://matrix.to/#/#embassy-rs:matrix.org diff --git a/examples/stm32l432/build.rs b/examples/stm32l432/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32l432/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32l432/src/bin/qspi_mmap.rs b/examples/stm32l432/src/bin/qspi_mmap.rs new file mode 100644 index 000000000..86a20eb3d --- /dev/null +++ b/examples/stm32l432/src/bin/qspi_mmap.rs @@ -0,0 +1,274 @@ +#![no_std] +#![no_main] +#![allow(dead_code)] +/// This example demonstrates how to use the QSPI peripheral in both indirect-mode and memory-mapped mode. +/// If you want to test this example, please pay attention to flash pins and check flash device datasheet +/// to make sure operations in this example are compatible with your device, especially registers I/O operations. +use defmt::info; +use embassy_stm32::mode; +use embassy_stm32::qspi::enums::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, MemorySize, QspiWidth, +}; +use embassy_stm32::qspi::{self, Instance, TransferConfig}; +pub struct FlashMemory { + qspi: qspi::Qspi<'static, I, mode::Async>, +} +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +const MEMORY_PAGE_SIZE: usize = 256; +const CMD_READ_SR: u8 = 0x05; +const CMD_READ_CR: u8 = 0x35; +const CMD_QUAD_READ: u8 = 0x6B; +const CMD_QUAD_WRITE_PG: u8 = 0x32; +const CMD_READ_ID: u8 = 0x9F; +const CMD_READ_MID: u8 = 0x90; +const CMD_READ_UUID: u8 = 0x4B; +const CMD_ENABLE_RESET: u8 = 0x66; +const CMD_RESET: u8 = 0x99; +const CMD_WRITE_ENABLE: u8 = 0x06; +const CMD_SECTOR_ERASE: u8 = 0x20; + +const CMD_WRITE_SR: u8 = 0x01; + +impl FlashMemory { + pub fn new(qspi: qspi::Qspi<'static, I, mode::Async>) -> Self { + let mut memory = Self { qspi }; + + memory.reset_memory(); + memory.enable_quad(); + + memory + } + fn enable_quad(&mut self) { + let sr = self.read_sr_lsb(); + let cr = self.read_sr_msb(); + + self.write_sr(sr, cr | 0x02); + } + fn read_register(&mut self, cmd: u8) -> u8 { + let mut buffer = [0; 1]; + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer[0] + } + + fn write_register(&mut self, cmd: u8, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_write(&buffer, transaction); + } + pub fn write_sr(&mut self, lsb: u8, msb: u8) { + let buffer = [lsb, msb]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: CMD_WRITE_SR, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_write(&buffer, transaction); + } + + pub fn read_sr_lsb(&mut self) -> u8 { + self.read_register(CMD_READ_SR) + } + pub fn read_sr_msb(&mut self) -> u8 { + self.read_register(CMD_READ_CR) + } + + pub fn reset_memory(&mut self) { + self.exec_command(CMD_ENABLE_RESET); + self.exec_command(CMD_RESET); + self.wait_write_finish(); + } + fn exec_command(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::NONE, + instruction: cmd, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_command(transaction); + } + fn wait_write_finish(&mut self) { + while (self.read_sr_lsb() & 0x01) != 0 {} + } + + pub fn read_mid(&mut self) -> [u8; 2] { + let mut buffer = [0; 2]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::SING, + instruction: CMD_READ_MID, + address: Some(0), + dummy: DummyCycles::_0, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer + } + pub fn read_uuid(&mut self) -> [u8; 16] { + let mut buffer = [0; 16]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::SING, + instruction: CMD_READ_UUID, + address: Some(0), + dummy: DummyCycles::_8, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer + } + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 3]; + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::SING, + instruction: CMD_READ_ID, + address: None, + dummy: DummyCycles::_0, + }; + self.qspi.blocking_read(&mut buffer, transaction); + buffer + } + + pub fn enable_mmap(&mut self) { + let transaction: TransferConfig = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::QUAD, + instruction: CMD_QUAD_READ, + address: Some(0), + dummy: DummyCycles::_8, + }; + self.qspi.enable_memory_map(&transaction); + } + fn perform_erase(&mut self, addr: u32, cmd: u8) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::NONE, + instruction: cmd, + address: Some(addr), + dummy: DummyCycles::_0, + }; + self.enable_write(); + self.qspi.blocking_command(transaction); + self.wait_write_finish(); + } + pub fn enable_write(&mut self) { + self.exec_command(CMD_WRITE_ENABLE); + } + pub fn erase_sector(&mut self, addr: u32) { + self.perform_erase(addr, CMD_SECTOR_ERASE); + } + fn write_page(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::QUAD, + instruction: CMD_QUAD_WRITE_PG, + address: Some(addr), + dummy: DummyCycles::_0, + }; + self.enable_write(); + if use_dma { + self.qspi.blocking_write_dma(buffer, transaction); + } else { + self.qspi.blocking_write(buffer, transaction); + } + self.wait_write_finish(); + } + pub fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page(place, chunk, chunk_size, use_dma); + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + pub fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: QspiWidth::SING, + awidth: QspiWidth::SING, + dwidth: QspiWidth::QUAD, + instruction: CMD_QUAD_READ, + address: Some(addr), + dummy: DummyCycles::_8, + }; + if use_dma { + self.qspi.blocking_read_dma(buffer, transaction); + } else { + self.qspi.blocking_read(buffer, transaction); + } + } +} + +const MEMORY_ADDR: u32 = 0x00000000 as u32; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let config = qspi::Config { + memory_size: MemorySize::_16MiB, + address_size: AddressSize::_24bit, + prescaler: 200, + cs_high_time: ChipSelectHighTime::_1Cycle, + fifo_threshold: FIFOThresholdLevel::_16Bytes, + }; + let driver = qspi::Qspi::new_bank1(p.QUADSPI, p.PB1, p.PB0, p.PA7, p.PA6, p.PA3, p.PA2, p.DMA2_CH7, config); + let mut flash = FlashMemory::new(driver); + let mut wr_buf = [0u8; 256]; + for i in 0..32 { + wr_buf[i] = i as u8; + } + let mut rd_buf = [0u8; 32]; + flash.erase_sector(MEMORY_ADDR); + flash.write_memory(MEMORY_ADDR, &wr_buf, false); + flash.read_memory(MEMORY_ADDR, &mut rd_buf, false); + + info!("data read from indirect mode: {}", rd_buf); + flash.enable_mmap(); + let qspi_base = unsafe { core::slice::from_raw_parts(0x9000_0000 as *const u8, 32) }; + info!("data read from mmap: {}", qspi_base); + loop { + Timer::after_millis(1000).await; + } +} diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 16c184de2..3ea3bcd5c 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -6,24 +6,23 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l552ze to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power", "dual-bank"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } usbd-hid = "0.8.1" -defmt = "0.3" -defmt-rtt = "0.4" -panic-probe = { version = "0.3", features = ["print-defmt"] } +defmt = "1.0.1" +defmt-rtt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" heapless = { version = "0.8", default-features = false } -rand_core = { version = "0.6.3", default-features = false } embedded-io-async = { version = "0.6.1" } static_cell = "2" diff --git a/examples/stm32l5/src/bin/stop.rs b/examples/stm32l5/src/bin/stop.rs index 32a736de8..d7a1efea9 100644 --- a/examples/stm32l5/src/bin/stop.rs +++ b/examples/stm32l5/src/bin/stop.rs @@ -7,7 +7,7 @@ use embassy_stm32::gpio::{AnyPin, Level, Output, Speed}; use embassy_stm32::low_power::Executor; use embassy_stm32::rcc::LsConfig; use embassy_stm32::rtc::{Rtc, RtcConfig}; -use embassy_stm32::Config; +use embassy_stm32::{Config, Peri}; use embassy_time::Timer; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -39,7 +39,7 @@ async fn async_main(spawner: Spawner) { } #[embassy_executor::task] -async fn blinky(led: AnyPin) -> ! { +async fn blinky(led: Peri<'static, AnyPin>) -> ! { let mut led = Output::new(led, Level::Low, Speed::Low); loop { info!("high"); diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index d02bac91d..6c72132c6 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::rng::Rng; use embassy_stm32::usb::Driver; use embassy_stm32::{bind_interrupts, peripherals, rng, usb, Config}; @@ -12,7 +12,6 @@ use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, UsbDevice}; use embedded_io_async::Write; -use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -36,8 +35,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -72,12 +71,6 @@ async fn main(spawner: Spawner) { config.max_power = 100; config.max_packet_size_0 = 64; - // Required for Windows support. - config.composite_with_iads = true; - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - // Create embassy-usb DeviceBuilder using the driver and config. static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new(); static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new(); @@ -121,11 +114,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/stm32u0/Cargo.toml b/examples/stm32u0/Cargo.toml index 2e890cdb5..3aa45dc79 100644 --- a/examples/stm32u0/Cargo.toml +++ b/examples/stm32u0/Cargo.toml @@ -6,20 +6,20 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32u083rc to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } micromath = "2.0.0" diff --git a/examples/stm32u0/src/bin/dac.rs b/examples/stm32u0/src/bin/dac.rs index fdbf1d374..50db0e082 100644 --- a/examples/stm32u0/src/bin/dac.rs +++ b/examples/stm32u0/src/bin/dac.rs @@ -3,7 +3,6 @@ use defmt::*; use embassy_stm32::dac::{DacCh1, Value}; -use embassy_stm32::dma::NoDma; use {defmt_rtt as _, panic_probe as _}; #[cortex_m_rt::entry] @@ -11,7 +10,7 @@ fn main() -> ! { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); + let mut dac = DacCh1::new_blocking(p.DAC1, p.PA4); loop { for v in 0..=255 { diff --git a/examples/stm32u0/src/bin/spi.rs b/examples/stm32u0/src/bin/spi.rs index 5693a3765..e03591daf 100644 --- a/examples/stm32u0/src/bin/spi.rs +++ b/examples/stm32u0/src/bin/spi.rs @@ -18,7 +18,7 @@ fn main() -> ! { let mut spi = Spi::new_blocking(p.SPI3, p.PC10, p.PC12, p.PC11, spi_config); - let mut cs = Output::new(p.PE0, Level::High, Speed::VeryHigh); + let mut cs = Output::new(p.PC13, Level::High, Speed::VeryHigh); loop { let mut buf = [0x0Au8; 4]; diff --git a/examples/stm32u5/.cargo/config.toml b/examples/stm32u5/.cargo/config.toml index 36c5b63a6..bdbd86354 100644 --- a/examples/stm32u5/.cargo/config.toml +++ b/examples/stm32u5/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32U585AIIx with your chip as listed in `probe-rs chip list` -runner = "probe-rs run --chip STM32U585AIIx" +# replace STM32U5G9ZJTxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32U5G9ZJTxQ" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index 20d64c6f7..777d3ed4c 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -5,22 +5,24 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -# Change stm32u585ai to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +# Change stm32u5g9zj to your chip name, if necessary. +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u5g9zj", "time-driver-any", "memory-x" ] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.4.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } +embedded-graphics = { version = "0.8.1" } +tinybmp = { version = "0.6.0" } micromath = "2.0.0" diff --git a/examples/stm32u5/src/bin/adc.rs b/examples/stm32u5/src/bin/adc.rs new file mode 100644 index 000000000..d2aa28087 --- /dev/null +++ b/examples/stm32u5/src/bin/adc.rs @@ -0,0 +1,109 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_stm32::adc; +use embassy_stm32::adc::{adc4, AdcChannel}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let config = embassy_stm32::Config::default(); + + let mut p = embassy_stm32::init(config); + + // **** ADC1 init **** + let mut adc1 = adc::Adc::new(p.ADC1); + let mut adc1_pin1 = p.PA3; // A0 on nucleo u5a5 + let mut adc1_pin2 = p.PA2; // A1 + adc1.set_resolution(adc::Resolution::BITS14); + adc1.set_averaging(adc::Averaging::Samples1024); + adc1.set_sample_time(adc::SampleTime::CYCLES160_5); + let max1 = adc::resolution_to_max_count(adc::Resolution::BITS14); + + // **** ADC2 init **** + let mut adc2 = adc::Adc::new(p.ADC2); + let mut adc2_pin1 = p.PC3; // A2 + let mut adc2_pin2 = p.PB0; // A3 + adc2.set_resolution(adc::Resolution::BITS14); + adc2.set_averaging(adc::Averaging::Samples1024); + adc2.set_sample_time(adc::SampleTime::CYCLES160_5); + let max2 = adc::resolution_to_max_count(adc::Resolution::BITS14); + + // **** ADC4 init **** + let mut adc4 = adc4::Adc4::new(p.ADC4); + let mut adc4_pin1 = p.PC1; // A4 + let mut adc4_pin2 = p.PC0; // A5 + adc4.set_resolution(adc4::Resolution::BITS12); + adc4.set_averaging(adc4::Averaging::Samples256); + adc4.set_sample_time(adc4::SampleTime::CYCLES1_5); + let max4 = adc4::resolution_to_max_count(adc4::Resolution::BITS12); + + // **** ADC1 blocking read **** + let raw: u16 = adc1.blocking_read(&mut adc1_pin1); + let volt: f32 = 3.3 * raw as f32 / max1 as f32; + info!("Read adc1 pin 1 {}", volt); + + let raw: u16 = adc1.blocking_read(&mut adc1_pin2); + let volt: f32 = 3.3 * raw as f32 / max1 as f32; + info!("Read adc1 pin 2 {}", volt); + + // **** ADC2 blocking read **** + let raw: u16 = adc2.blocking_read(&mut adc2_pin1); + let volt: f32 = 3.3 * raw as f32 / max2 as f32; + info!("Read adc2 pin 1 {}", volt); + + let raw: u16 = adc2.blocking_read(&mut adc2_pin2); + let volt: f32 = 3.3 * raw as f32 / max2 as f32; + info!("Read adc2 pin 2 {}", volt); + + // **** ADC4 blocking read **** + let raw: u16 = adc4.blocking_read(&mut adc4_pin1); + let volt: f32 = 3.3 * raw as f32 / max4 as f32; + info!("Read adc4 pin 1 {}", volt); + + let raw: u16 = adc4.blocking_read(&mut adc4_pin2); + let volt: f32 = 3.3 * raw as f32 / max4 as f32; + info!("Read adc4 pin 2 {}", volt); + + // **** ADC1 async read **** + let mut degraded11 = adc1_pin1.degrade_adc(); + let mut degraded12 = adc1_pin2.degrade_adc(); + let mut measurements = [0u16; 2]; + + adc1.read( + p.GPDMA1_CH0.reborrow(), + [ + (&mut degraded11, adc::SampleTime::CYCLES160_5), + (&mut degraded12, adc::SampleTime::CYCLES160_5), + ] + .into_iter(), + &mut measurements, + ) + .await; + let volt1: f32 = 3.3 * measurements[0] as f32 / max1 as f32; + let volt2: f32 = 3.3 * measurements[1] as f32 / max1 as f32; + + info!("Async read 1 pin 1 {}", volt1); + info!("Async read 1 pin 2 {}", volt2); + + // **** ADC2 does not support async read **** + + // **** ADC4 async read **** + let mut degraded41 = adc4_pin1.degrade_adc(); + let mut degraded42 = adc4_pin2.degrade_adc(); + let mut measurements = [0u16; 2]; + + // The channels must be in ascending order and can't repeat for ADC4 + adc4.read( + p.GPDMA1_CH1.reborrow(), + [&mut degraded42, &mut degraded41].into_iter(), + &mut measurements, + ) + .await + .unwrap(); + let volt2: f32 = 3.3 * measurements[0] as f32 / max4 as f32; + let volt1: f32 = 3.3 * measurements[1] as f32 / max4 as f32; + info!("Async read 4 pin 1 {}", volt1); + info!("Async read 4 pin 2 {}", volt2); +} diff --git a/examples/stm32u5/src/bin/blinky.rs b/examples/stm32u5/src/bin/blinky.rs index 7fe88c183..1fdfc7679 100644 --- a/examples/stm32u5/src/bin/blinky.rs +++ b/examples/stm32u5/src/bin/blinky.rs @@ -12,7 +12,8 @@ async fn main(_spawner: Spawner) -> ! { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let mut led = Output::new(p.PH7, Level::Low, Speed::Medium); + // replace PC13 with the right pin for your board. + let mut led = Output::new(p.PC13, Level::Low, Speed::Medium); loop { defmt::info!("on!"); diff --git a/examples/stm32u5/src/bin/ferris.bmp b/examples/stm32u5/src/bin/ferris.bmp new file mode 100644 index 000000000..7a222ab84 Binary files /dev/null and b/examples/stm32u5/src/bin/ferris.bmp differ diff --git a/examples/stm32u5/src/bin/hspi_memory_mapped.rs b/examples/stm32u5/src/bin/hspi_memory_mapped.rs new file mode 100644 index 000000000..9fef4855e --- /dev/null +++ b/examples/stm32u5/src/bin/hspi_memory_mapped.rs @@ -0,0 +1,455 @@ +#![no_main] +#![no_std] + +// Tested on an STM32U5G9J-DK2 demo board using the on-board MX66LM1G45G flash memory +// The flash is connected to the HSPI1 port as an OCTA-DTR device +// +// Use embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ" + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::hspi::{ + AddressSize, ChipSelectHighTime, DummyCycles, FIFOThresholdLevel, Hspi, HspiWidth, Instance, MemorySize, + MemoryType, TransferConfig, WrapSize, +}; +use embassy_stm32::mode::Async; +use embassy_stm32::rcc; +use embassy_stm32::time::Hertz; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start hspi_memory_mapped"); + + // RCC config + let mut config = embassy_stm32::Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV1, + mul: rcc::PllMul::MUL10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV1), + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz + config.rcc.pll2 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV4, + mul: rcc::PllMul::MUL66, + divp: None, + divq: Some(rcc::PllDiv::DIV2), + divr: None, + }); + config.rcc.mux.hspi1sel = rcc::mux::Hspisel::PLL2_Q; // 132 MHz + + // Initialize peripherals + let p = embassy_stm32::init(config); + + let flash_config = embassy_stm32::hspi::Config { + fifo_threshold: FIFOThresholdLevel::_4Bytes, + memory_type: MemoryType::Macronix, + device_size: MemorySize::_1GiB, + chip_select_high_time: ChipSelectHighTime::_2Cycle, + free_running_clock: false, + clock_mode: false, + wrap_size: WrapSize::None, + clock_prescaler: 0, + sample_shifting: false, + delay_hold_quarter_cycle: false, + chip_select_boundary: 0, + delay_block_bypass: false, + max_transfer: 0, + refresh: 0, + }; + + let use_dma = true; + + info!("Testing flash in OCTA DTR mode and memory mapped mode"); + + let hspi = Hspi::new_octospi( + p.HSPI1, + p.PI3, + p.PH10, + p.PH11, + p.PH12, + p.PH13, + p.PH14, + p.PH15, + p.PI0, + p.PI1, + p.PH9, + p.PI2, + p.GPDMA1_CH7, + flash_config, + ); + + let mut flash = OctaDtrFlashMemory::new(hspi).await; + + let flash_id = flash.read_id(); + info!("FLASH ID: {=[u8]:x}", flash_id); + + let mut rd_buf = [0u8; 16]; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + + flash.erase_sector(0).await; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + assert_eq!(rd_buf[0], 0xFF); + assert_eq!(rd_buf[15], 0xFF); + + let mut wr_buf = [0u8; 16]; + for i in 0..wr_buf.len() { + wr_buf[i] = i as u8; + } + info!("WRITE BUF: {=[u8]:#X}", wr_buf); + flash.write_memory(0, &wr_buf, use_dma).await; + flash.read_memory(0, &mut rd_buf, use_dma).await; + info!("READ BUF: {=[u8]:#X}", rd_buf); + assert_eq!(rd_buf[0], 0x00); + assert_eq!(rd_buf[15], 0x0F); + + flash.enable_mm().await; + info!("Enabled memory mapped mode"); + + let first_u32 = unsafe { *(0xA0000000 as *const u32) }; + info!("first_u32: 0x{=u32:X}", first_u32); + assert_eq!(first_u32, 0x03020100); + + let second_u32 = unsafe { *(0xA0000004 as *const u32) }; + assert_eq!(second_u32, 0x07060504); + info!("second_u32: 0x{=u32:X}", second_u32); + + let first_u8 = unsafe { *(0xA0000000 as *const u8) }; + assert_eq!(first_u8, 00); + info!("first_u8: 0x{=u8:X}", first_u8); + + let second_u8 = unsafe { *(0xA0000001 as *const u8) }; + assert_eq!(second_u8, 0x01); + info!("second_u8: 0x{=u8:X}", second_u8); + + let third_u8 = unsafe { *(0xA0000002 as *const u8) }; + assert_eq!(third_u8, 0x02); + info!("third_u8: 0x{=u8:X}", third_u8); + + let fourth_u8 = unsafe { *(0xA0000003 as *const u8) }; + assert_eq!(fourth_u8, 0x03); + info!("fourth_u8: 0x{=u8:X}", fourth_u8); + + info!("DONE"); +} + +// Custom implementation for MX66UW1G45G NOR flash memory from Macronix. +// Chip commands are hardcoded as they depend on the chip used. +// This implementation enables Octa I/O (OPI) and Double Transfer Rate (DTR) + +pub struct OctaDtrFlashMemory<'d, I: Instance> { + hspi: Hspi<'d, I, Async>, +} + +impl<'d, I: Instance> OctaDtrFlashMemory<'d, I> { + const MEMORY_PAGE_SIZE: usize = 256; + + const CMD_READ_OCTA_DTR: u16 = 0xEE11; + const CMD_PAGE_PROGRAM_OCTA_DTR: u16 = 0x12ED; + + const CMD_READ_ID_OCTA_DTR: u16 = 0x9F60; + + const CMD_RESET_ENABLE: u8 = 0x66; + const CMD_RESET_ENABLE_OCTA_DTR: u16 = 0x6699; + const CMD_RESET: u8 = 0x99; + const CMD_RESET_OCTA_DTR: u16 = 0x9966; + + const CMD_WRITE_ENABLE: u8 = 0x06; + const CMD_WRITE_ENABLE_OCTA_DTR: u16 = 0x06F9; + + const CMD_SECTOR_ERASE_OCTA_DTR: u16 = 0x21DE; + const CMD_BLOCK_ERASE_OCTA_DTR: u16 = 0xDC23; + + const CMD_READ_SR: u8 = 0x05; + const CMD_READ_SR_OCTA_DTR: u16 = 0x05FA; + + const CMD_READ_CR2: u8 = 0x71; + const CMD_WRITE_CR2: u8 = 0x72; + + const CR2_REG1_ADDR: u32 = 0x00000000; + const CR2_OCTA_DTR: u8 = 0x02; + + const CR2_REG3_ADDR: u32 = 0x00000300; + const CR2_DC_6_CYCLES: u8 = 0x07; + + pub async fn new(hspi: Hspi<'d, I, Async>) -> Self { + let mut memory = Self { hspi }; + + memory.reset_memory().await; + memory.enable_octa_dtr().await; + memory + } + + async fn enable_octa_dtr(&mut self) { + self.write_enable_spi().await; + self.write_cr2_spi(Self::CR2_REG3_ADDR, Self::CR2_DC_6_CYCLES); + self.write_enable_spi().await; + self.write_cr2_spi(Self::CR2_REG1_ADDR, Self::CR2_OCTA_DTR); + } + + pub async fn enable_mm(&mut self) { + let read_config = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_6, + ..Default::default() + }; + + let write_config = TransferConfig { + iwidth: HspiWidth::OCTO, + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + ..Default::default() + }; + self.hspi.enable_memory_mapped_mode(read_config, write_config).unwrap(); + } + + async fn exec_command_spi(&mut self, cmd: u8) { + let transaction = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(cmd as u32), + ..Default::default() + }; + info!("Excuting command: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_command(&transaction).unwrap(); + } + + async fn exec_command_octa_dtr(&mut self, cmd: u16) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(cmd as u32), + isize: AddressSize::_16Bit, + idtr: true, + ..Default::default() + }; + info!("Excuting command: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_command(&transaction).unwrap(); + } + + fn wait_write_finish_spi(&mut self) { + while (self.read_sr_spi() & 0x01) != 0 {} + } + + fn wait_write_finish_octa_dtr(&mut self) { + while (self.read_sr_octa_dtr() & 0x01) != 0 {} + } + + pub async fn reset_memory(&mut self) { + // servono entrambi i comandi? + self.exec_command_octa_dtr(Self::CMD_RESET_ENABLE_OCTA_DTR).await; + self.exec_command_octa_dtr(Self::CMD_RESET_OCTA_DTR).await; + self.exec_command_spi(Self::CMD_RESET_ENABLE).await; + self.exec_command_spi(Self::CMD_RESET).await; + self.wait_write_finish_spi(); + } + + async fn write_enable_spi(&mut self) { + self.exec_command_spi(Self::CMD_WRITE_ENABLE).await; + } + + async fn write_enable_octa_dtr(&mut self) { + self.exec_command_octa_dtr(Self::CMD_WRITE_ENABLE_OCTA_DTR).await; + } + + pub fn read_id(&mut self) -> [u8; 3] { + let mut buffer = [0; 6]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_ID_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(0), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_5, + ..Default::default() + }; + info!("Reading flash id: 0x{:X}", transaction.instruction.unwrap()); + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + [buffer[0], buffer[2], buffer[4]] + } + + pub async fn read_memory(&mut self, addr: u32, buffer: &mut [u8], use_dma: bool) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_6, + ..Default::default() + }; + if use_dma { + self.hspi.read(buffer, transaction).await.unwrap(); + } else { + self.hspi.blocking_read(buffer, transaction).unwrap(); + } + } + + async fn perform_erase_octa_dtr(&mut self, addr: u32, cmd: u16) { + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(cmd as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + ..Default::default() + }; + self.write_enable_octa_dtr().await; + self.hspi.blocking_command(&transaction).unwrap(); + self.wait_write_finish_octa_dtr(); + } + + pub async fn erase_sector(&mut self, addr: u32) { + info!("Erasing 4K sector at address: 0x{:X}", addr); + self.perform_erase_octa_dtr(addr, Self::CMD_SECTOR_ERASE_OCTA_DTR).await; + } + + pub async fn erase_block(&mut self, addr: u32) { + info!("Erasing 64K block at address: 0x{:X}", addr); + self.perform_erase_octa_dtr(addr, Self::CMD_BLOCK_ERASE_OCTA_DTR).await; + } + + async fn write_page_octa_dtr(&mut self, addr: u32, buffer: &[u8], len: usize, use_dma: bool) { + assert!( + (len as u32 + (addr & 0x000000ff)) <= Self::MEMORY_PAGE_SIZE as u32, + "write_page(): page write length exceeds page boundary (len = {}, addr = {:X}", + len, + addr + ); + + let transaction = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_PAGE_PROGRAM_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(addr), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + ..Default::default() + }; + self.write_enable_octa_dtr().await; + if use_dma { + self.hspi.write(buffer, transaction).await.unwrap(); + } else { + self.hspi.blocking_write(buffer, transaction).unwrap(); + } + self.wait_write_finish_octa_dtr(); + } + + pub async fn write_memory(&mut self, addr: u32, buffer: &[u8], use_dma: bool) { + let mut left = buffer.len(); + let mut place = addr; + let mut chunk_start = 0; + + while left > 0 { + let max_chunk_size = Self::MEMORY_PAGE_SIZE - (place & 0x000000ff) as usize; + let chunk_size = if left >= max_chunk_size { max_chunk_size } else { left }; + let chunk = &buffer[chunk_start..(chunk_start + chunk_size)]; + self.write_page_octa_dtr(place, chunk, chunk_size, use_dma).await; + place += chunk_size as u32; + left -= chunk_size; + chunk_start += chunk_size; + } + } + + pub fn read_sr_spi(&mut self) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_READ_SR as u32), + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G SR register: 0x{:x}", buffer[0]); + buffer[0] + } + + pub fn read_sr_octa_dtr(&mut self) -> u8 { + let mut buffer = [0; 2]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::OCTO, + instruction: Some(Self::CMD_READ_SR_OCTA_DTR as u32), + isize: AddressSize::_16Bit, + idtr: true, + adwidth: HspiWidth::OCTO, + address: Some(0), + adsize: AddressSize::_32Bit, + addtr: true, + dwidth: HspiWidth::OCTO, + ddtr: true, + dummy: DummyCycles::_5, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G SR register: 0x{:x}", buffer[0]); + buffer[0] + } + + pub fn read_cr2_spi(&mut self, addr: u32) -> u8 { + let mut buffer = [0; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_READ_CR2 as u32), + adwidth: HspiWidth::SING, + address: Some(addr), + adsize: AddressSize::_32Bit, + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_read(&mut buffer, transaction).unwrap(); + // info!("Read MX66LM1G45G CR2[0x{:X}] register: 0x{:x}", addr, buffer[0]); + buffer[0] + } + + pub fn write_cr2_spi(&mut self, addr: u32, value: u8) { + let buffer = [value; 1]; + let transaction: TransferConfig = TransferConfig { + iwidth: HspiWidth::SING, + instruction: Some(Self::CMD_WRITE_CR2 as u32), + adwidth: HspiWidth::SING, + address: Some(addr), + adsize: AddressSize::_32Bit, + dwidth: HspiWidth::SING, + ..Default::default() + }; + self.hspi.blocking_write(&buffer, transaction).unwrap(); + } +} diff --git a/examples/stm32u5/src/bin/i2c.rs b/examples/stm32u5/src/bin/i2c.rs index 19a78eac9..d5f5d6f60 100644 --- a/examples/stm32u5/src/bin/i2c.rs +++ b/examples/stm32u5/src/bin/i2c.rs @@ -13,7 +13,7 @@ const WHOAMI: u8 = 0x0F; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut i2c = I2c::new_blocking(p.I2C2, p.PH4, p.PH5, Hertz(100_000), Default::default()); + let mut i2c = I2c::new_blocking(p.I2C2, p.PF1, p.PF0, Hertz(100_000), Default::default()); let mut data = [0u8; 1]; unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); diff --git a/examples/stm32u5/src/bin/ltdc.rs b/examples/stm32u5/src/bin/ltdc.rs new file mode 100644 index 000000000..bd59a9148 --- /dev/null +++ b/examples/stm32u5/src/bin/ltdc.rs @@ -0,0 +1,461 @@ +#![no_std] +#![no_main] +#![macro_use] +#![allow(static_mut_refs)] + +/// This example was derived from examples\stm32h735\src\bin\ltdc.rs +/// It demonstrates the LTDC lcd display peripheral and was tested on an STM32U5G9J-DK2 demo board (embassy-stm32 feature "stm32u5g9zj" and probe-rs chip "STM32U5G9ZJTxQ") +/// +use bouncy_box::BouncyBox; +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::ltdc::{self, Ltdc, LtdcConfiguration, LtdcLayer, LtdcLayerConfig, PolarityActive, PolarityEdge}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::{Duration, Timer}; +use embedded_graphics::draw_target::DrawTarget; +use embedded_graphics::geometry::{OriginDimensions, Point, Size}; +use embedded_graphics::image::Image; +use embedded_graphics::pixelcolor::raw::RawU24; +use embedded_graphics::pixelcolor::Rgb888; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::Rectangle; +use embedded_graphics::Pixel; +use heapless::{Entry, FnvIndexMap}; +use tinybmp::Bmp; +use {defmt_rtt as _, panic_probe as _}; + +const DISPLAY_WIDTH: usize = 800; +const DISPLAY_HEIGHT: usize = 480; +const MY_TASK_POOL_SIZE: usize = 2; + +// the following two display buffers consume 261120 bytes that just about fits into axis ram found on the mcu +pub static mut FB1: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; +pub static mut FB2: [TargetPixelType; DISPLAY_WIDTH * DISPLAY_HEIGHT] = [0; DISPLAY_WIDTH * DISPLAY_HEIGHT]; + +bind_interrupts!(struct Irqs { + LTDC => ltdc::InterruptHandler; +}); + +const NUM_COLORS: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = rcc_setup::stm32u5g9zj_init(); + + // enable ICACHE + embassy_stm32::pac::ICACHE.cr().write(|w| { + w.set_en(true); + }); + + // blink the led on another task + let led = Output::new(p.PD2, Level::High, Speed::Low); + unwrap!(spawner.spawn(led_task(led))); + + // numbers from STM32U5G9J-DK2.ioc + const RK050HR18H_HSYNC: u16 = 5; // Horizontal synchronization + const RK050HR18H_HBP: u16 = 8; // Horizontal back porch + const RK050HR18H_HFP: u16 = 8; // Horizontal front porch + const RK050HR18H_VSYNC: u16 = 5; // Vertical synchronization + const RK050HR18H_VBP: u16 = 8; // Vertical back porch + const RK050HR18H_VFP: u16 = 8; // Vertical front porch + + // NOTE: all polarities have to be reversed with respect to the STM32U5G9J-DK2 CubeMX parametrization + let ltdc_config = LtdcConfiguration { + active_width: DISPLAY_WIDTH as _, + active_height: DISPLAY_HEIGHT as _, + h_back_porch: RK050HR18H_HBP, + h_front_porch: RK050HR18H_HFP, + v_back_porch: RK050HR18H_VBP, + v_front_porch: RK050HR18H_VFP, + h_sync: RK050HR18H_HSYNC, + v_sync: RK050HR18H_VSYNC, + h_sync_polarity: PolarityActive::ActiveHigh, + v_sync_polarity: PolarityActive::ActiveHigh, + data_enable_polarity: PolarityActive::ActiveHigh, + pixel_clock_polarity: PolarityEdge::RisingEdge, + }; + + info!("init ltdc"); + let mut ltdc_de = Output::new(p.PD6, Level::Low, Speed::High); + let mut ltdc_disp_ctrl = Output::new(p.PE4, Level::Low, Speed::High); + let mut ltdc_bl_ctrl = Output::new(p.PE6, Level::Low, Speed::High); + let mut ltdc = Ltdc::new_with_pins( + p.LTDC, // PERIPHERAL + Irqs, // IRQS + p.PD3, // CLK + p.PE0, // HSYNC + p.PD13, // VSYNC + p.PB9, // B0 + p.PB2, // B1 + p.PD14, // B2 + p.PD15, // B3 + p.PD0, // B4 + p.PD1, // B5 + p.PE7, // B6 + p.PE8, // B7 + p.PC8, // G0 + p.PC9, // G1 + p.PE9, // G2 + p.PE10, // G3 + p.PE11, // G4 + p.PE12, // G5 + p.PE13, // G6 + p.PE14, // G7 + p.PC6, // R0 + p.PC7, // R1 + p.PE15, // R2 + p.PD8, // R3 + p.PD9, // R4 + p.PD10, // R5 + p.PD11, // R6 + p.PD12, // R7 + ); + ltdc.init(<dc_config); + ltdc_de.set_low(); + ltdc_bl_ctrl.set_high(); + ltdc_disp_ctrl.set_high(); + + // we only need to draw on one layer for this example (not to be confused with the double buffer) + info!("enable bottom layer"); + let layer_config = LtdcLayerConfig { + pixel_format: ltdc::PixelFormat::L8, // 1 byte per pixel + layer: LtdcLayer::Layer1, + window_x0: 0, + window_x1: DISPLAY_WIDTH as _, + window_y0: 0, + window_y1: DISPLAY_HEIGHT as _, + }; + + let ferris_bmp: Bmp = Bmp::from_slice(include_bytes!("./ferris.bmp")).unwrap(); + let color_map = build_color_lookup_map(&ferris_bmp); + let clut = build_clut(&color_map); + + // enable the bottom layer with a 256 color lookup table + ltdc.init_layer(&layer_config, Some(&clut)); + + // Safety: the DoubleBuffer controls access to the statically allocated frame buffers + // and it is the only thing that mutates their content + let mut double_buffer = DoubleBuffer::new( + unsafe { FB1.as_mut() }, + unsafe { FB2.as_mut() }, + layer_config, + color_map, + ); + + // this allows us to perform some simple animation for every frame + let mut bouncy_box = BouncyBox::new( + ferris_bmp.bounding_box(), + Rectangle::new(Point::zero(), Size::new(DISPLAY_WIDTH as u32, DISPLAY_HEIGHT as u32)), + 2, + ); + + loop { + // cpu intensive drawing to the buffer that is NOT currently being copied to the LCD screen + double_buffer.clear(); + let position = bouncy_box.next_point(); + let ferris = Image::new(&ferris_bmp, position); + unwrap!(ferris.draw(&mut double_buffer)); + + // perform async dma data transfer to the lcd screen + unwrap!(double_buffer.swap(&mut ltdc).await); + } +} + +/// builds the color look-up table from all unique colors found in the bitmap. This should be a 256 color indexed bitmap to work. +fn build_color_lookup_map(bmp: &Bmp) -> FnvIndexMap { + let mut color_map: FnvIndexMap = heapless::FnvIndexMap::new(); + let mut counter: u8 = 0; + + // add black to position 0 + color_map.insert(Rgb888::new(0, 0, 0).into_storage(), counter).unwrap(); + counter += 1; + + for Pixel(_point, color) in bmp.pixels() { + let raw = color.into_storage(); + if let Entry::Vacant(v) = color_map.entry(raw) { + v.insert(counter).expect("more than 256 colors detected"); + counter += 1; + } + } + color_map +} + +/// builds the color look-up table from the color map provided +fn build_clut(color_map: &FnvIndexMap) -> [ltdc::RgbColor; NUM_COLORS] { + let mut clut = [ltdc::RgbColor::default(); NUM_COLORS]; + for (color, index) in color_map.iter() { + let color = Rgb888::from(RawU24::new(*color)); + clut[*index as usize] = ltdc::RgbColor { + red: color.r(), + green: color.g(), + blue: color.b(), + }; + } + + clut +} + +#[embassy_executor::task(pool_size = MY_TASK_POOL_SIZE)] +async fn led_task(mut led: Output<'static>) { + let mut counter = 0; + loop { + info!("blink: {}", counter); + counter += 1; + + // on + led.set_low(); + Timer::after(Duration::from_millis(50)).await; + + // off + led.set_high(); + Timer::after(Duration::from_millis(450)).await; + } +} + +pub type TargetPixelType = u8; + +// A simple double buffer +pub struct DoubleBuffer { + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + is_buf0: bool, + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, +} + +impl DoubleBuffer { + pub fn new( + buf0: &'static mut [TargetPixelType], + buf1: &'static mut [TargetPixelType], + layer_config: LtdcLayerConfig, + color_map: FnvIndexMap, + ) -> Self { + Self { + buf0, + buf1, + is_buf0: true, + layer_config, + color_map, + } + } + + pub fn current(&mut self) -> (&FnvIndexMap, &mut [TargetPixelType]) { + if self.is_buf0 { + (&self.color_map, self.buf0) + } else { + (&self.color_map, self.buf1) + } + } + + pub async fn swap(&mut self, ltdc: &mut Ltdc<'_, T>) -> Result<(), ltdc::Error> { + let (_, buf) = self.current(); + let frame_buffer = buf.as_ptr(); + self.is_buf0 = !self.is_buf0; + ltdc.set_buffer(self.layer_config.layer, frame_buffer as *const _).await + } + + /// Clears the buffer + pub fn clear(&mut self) { + let (color_map, buf) = self.current(); + let black = Rgb888::new(0, 0, 0).into_storage(); + let color_index = color_map.get(&black).expect("no black found in the color map"); + + for a in buf.iter_mut() { + *a = *color_index; // solid black + } + } +} + +// Implement DrawTarget for +impl DrawTarget for DoubleBuffer { + type Color = Rgb888; + type Error = (); + + /// Draw a pixel + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let size = self.size(); + let width = size.width as i32; + let height = size.height as i32; + let (color_map, buf) = self.current(); + + for pixel in pixels { + let Pixel(point, color) = pixel; + + if point.x >= 0 && point.y >= 0 && point.x < width && point.y < height { + let index = point.y * width + point.x; + let raw_color = color.into_storage(); + + match color_map.get(&raw_color) { + Some(x) => { + buf[index as usize] = *x; + } + None => panic!("color not found in color map: {}", raw_color), + }; + } else { + // Ignore invalid points + } + } + + Ok(()) + } +} + +impl OriginDimensions for DoubleBuffer { + /// Return the size of the display + fn size(&self) -> Size { + Size::new( + (self.layer_config.window_x1 - self.layer_config.window_x0) as _, + (self.layer_config.window_y1 - self.layer_config.window_y0) as _, + ) + } +} + +mod rcc_setup { + + use embassy_stm32::time::Hertz; + use embassy_stm32::{rcc, Config, Peripherals}; + + /// Sets up clocks for the stm32u5g9zj mcu + /// change this if you plan to use a different microcontroller + pub fn stm32u5g9zj_init() -> Peripherals { + // setup power and clocks for an STM32U5G9J-DK2 run from an external 16 Mhz external oscillator + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: Hertz(16_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.pll1 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV1, + mul: rcc::PllMul::MUL10, + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV1), + }); + config.rcc.sys = rcc::Sysclk::PLL1_R; // 160 Mhz + config.rcc.pll3 = Some(rcc::Pll { + source: rcc::PllSource::HSE, + prediv: rcc::PllPreDiv::DIV4, // PLL_M + mul: rcc::PllMul::MUL125, // PLL_N + divp: None, + divq: None, + divr: Some(rcc::PllDiv::DIV20), + }); + config.rcc.mux.ltdcsel = rcc::mux::Ltdcsel::PLL3_R; // 25 MHz + embassy_stm32::init(config) + } +} + +mod bouncy_box { + use embedded_graphics::geometry::Point; + use embedded_graphics::primitives::Rectangle; + + enum Direction { + DownLeft, + DownRight, + UpLeft, + UpRight, + } + + pub struct BouncyBox { + direction: Direction, + child_rect: Rectangle, + parent_rect: Rectangle, + current_point: Point, + move_by: usize, + } + + // This calculates the coordinates of a chile rectangle bounced around inside a parent bounded box + impl BouncyBox { + pub fn new(child_rect: Rectangle, parent_rect: Rectangle, move_by: usize) -> Self { + let center_box = parent_rect.center(); + let center_img = child_rect.center(); + let current_point = Point::new(center_box.x - center_img.x / 2, center_box.y - center_img.y / 2); + Self { + direction: Direction::DownRight, + child_rect, + parent_rect, + current_point, + move_by, + } + } + + pub fn next_point(&mut self) -> Point { + let direction = &self.direction; + let img_height = self.child_rect.size.height as i32; + let box_height = self.parent_rect.size.height as i32; + let img_width = self.child_rect.size.width as i32; + let box_width = self.parent_rect.size.width as i32; + let move_by = self.move_by as i32; + + match direction { + Direction::DownLeft => { + self.current_point.x -= move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } + } + Direction::DownRight => { + self.current_point.x += move_by; + self.current_point.y += move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = (self.current_point.y + img_height) > box_height; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::DownLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::UpRight + } + } + Direction::UpLeft => { + self.current_point.x -= move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = self.current_point.x < 0; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpRight + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } + } + Direction::UpRight => { + self.current_point.x += move_by; + self.current_point.y -= move_by; + + let x_out_of_bounds = (self.current_point.x + img_width) > box_width; + let y_out_of_bounds = self.current_point.y < 0; + + if x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownLeft + } else if x_out_of_bounds && !y_out_of_bounds { + self.direction = Direction::UpLeft + } else if !x_out_of_bounds && y_out_of_bounds { + self.direction = Direction::DownRight + } + } + } + + self.current_point + } + } +} diff --git a/examples/stm32u5/src/bin/tsc.rs b/examples/stm32u5/src/bin/tsc.rs index eb15d275a..a85acc4c7 100644 --- a/examples/stm32u5/src/bin/tsc.rs +++ b/examples/stm32u5/src/bin/tsc.rs @@ -2,8 +2,8 @@ #![no_main] use defmt::*; -use embassy_stm32::bind_interrupts; use embassy_stm32::tsc::{self, *}; +use embassy_stm32::{bind_interrupts, peripherals}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -33,63 +33,52 @@ async fn main(_spawner: embassy_executor::Spawner) { synchro_pin_polarity: false, acquisition_mode: false, max_count_interrupt: false, - channel_ios: TscIOPin::Group2Io2 | TscIOPin::Group7Io3, - shield_ios: TscIOPin::Group1Io3.into(), - sampling_ios: TscIOPin::Group1Io2 | TscIOPin::Group2Io1 | TscIOPin::Group7Io2, }; - let mut g1: PinGroup = PinGroup::new(); - g1.set_io2(context.PB13, PinType::Sample); - g1.set_io3(context.PB14, PinType::Shield); + let mut g1: PinGroupWithRoles = PinGroupWithRoles::default(); + g1.set_io2::(context.PB13); + g1.set_io3::(context.PB14); - let mut g2: PinGroup = PinGroup::new(); - g2.set_io1(context.PB4, PinType::Sample); - g2.set_io2(context.PB5, PinType::Channel); + let mut g2: PinGroupWithRoles = PinGroupWithRoles::default(); + g2.set_io1::(context.PB4); + let sensor0 = g2.set_io2(context.PB5); - let mut g7: PinGroup = PinGroup::new(); - g7.set_io2(context.PE3, PinType::Sample); - g7.set_io3(context.PE4, PinType::Channel); + let mut g7: PinGroupWithRoles = PinGroupWithRoles::default(); + g7.set_io2::(context.PE3); + let sensor1 = g7.set_io3(context.PE4); - let mut touch_controller = tsc::Tsc::new_async( - context.TSC, - Some(g1), - Some(g2), - None, - None, - None, - None, - Some(g7), - None, - config, - Irqs, - ); + let pin_groups: PinGroups = PinGroups { + g1: Some(g1.pin_group), + g2: Some(g2.pin_group), + g7: Some(g7.pin_group), + ..Default::default() + }; - touch_controller.discharge_io(true); - Timer::after_millis(1).await; + let mut touch_controller = tsc::Tsc::new_async(context.TSC, pin_groups, config, Irqs).unwrap(); - touch_controller.start(); + let acquisition_bank = touch_controller.create_acquisition_bank(AcquisitionBankPins { + g2_pin: Some(sensor0), + g7_pin: Some(sensor1), + ..Default::default() + }); + + touch_controller.set_active_channels_bank(&acquisition_bank); - let mut group_two_val = 0; - let mut group_seven_val = 0; info!("Starting touch_controller interface"); loop { + touch_controller.start(); touch_controller.pend_for_acquisition().await; touch_controller.discharge_io(true); Timer::after_millis(1).await; - if touch_controller.group_get_status(Group::Two) == GroupStatus::Complete { - group_two_val = touch_controller.group_get_value(Group::Two); + let status = touch_controller.get_acquisition_bank_status(&acquisition_bank); + + if status.all_complete() { + let read_values = touch_controller.get_acquisition_bank_values(&acquisition_bank); + let group2_reading = read_values.get_group_reading(Group::Two).unwrap(); + let group7_reading = read_values.get_group_reading(Group::Seven).unwrap(); + info!("group 2 value: {}", group2_reading.sensor_value); + info!("group 7 value: {}", group7_reading.sensor_value); } - - if touch_controller.group_get_status(Group::Seven) == GroupStatus::Complete { - group_seven_val = touch_controller.group_get_value(Group::Seven); - } - - info!( - "Group Two value: {}, Group Seven value: {},", - group_two_val, group_seven_val - ); - - touch_controller.start(); } } diff --git a/examples/stm32u5/src/bin/usb_hs_serial.rs b/examples/stm32u5/src/bin/usb_hs_serial.rs new file mode 100644 index 000000000..d37e7777b --- /dev/null +++ b/examples/stm32u5/src/bin/usb_hs_serial.rs @@ -0,0 +1,122 @@ +#![no_std] +#![no_main] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_HS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + use embassy_stm32::time::Hertz; + config.rcc.hse = Some(Hse { + freq: Hertz(16_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, // HSE / 2 = 8MHz + mul: PllMul::MUL60, // 8MHz * 60 = 480MHz + divr: Some(PllDiv::DIV3), // 480MHz / 3 = 160MHz (sys_ck) + divq: Some(PllDiv::DIV10), // 480MHz / 10 = 48MHz (USB) + divp: Some(PllDiv::DIV15), // 480MHz / 15 = 32MHz (USBOTG) + }); + config.rcc.mux.otghssel = mux::Otghssel::PLL1_P; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.sys = Sysclk::PLL1_R; + } + + 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::Config::default(); + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + config.vbus_detection = false; + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut 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); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index 4d56395da..ff7f4e5be 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -13,7 +13,7 @@ use embassy_usb::Builder; use panic_probe as _; bind_interrupts!(struct Irqs { - OTG_FS => usb::InterruptHandler; + OTG_HS => usb::InterruptHandler; }); #[embassy_executor::main] @@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) { // to enable vbus_detection to comply with the USB spec. If you enable it, the board // has to support it or USB won't work at all. See docs on `vbus_detection` for details. config.vbus_detection = false; - let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + let driver = Driver::new_hs(p.USB_OTG_HS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); // Create embassy-usb Config let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); @@ -56,13 +56,6 @@ async fn main(_spawner: Spawner) { config.product = Some("USB-serial example"); config.serial_number = Some("12345678"); - // Required for windows compatibility. - // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - config.composite_with_iads = true; - // Create embassy-usb DeviceBuilder using the driver and config. // It needs some buffers for building the descriptors. let mut config_descriptor = [0; 256]; diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 1e1a0efe2..dbe9660e2 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -6,20 +6,20 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32wb55rg to your chip name in both dependencies, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional = true } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +embedded-hal = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } static_cell = "2" diff --git a/examples/stm32wb/src/bin/gatt_server.rs b/examples/stm32wb/src/bin/gatt_server.rs index 1cc50e134..041dc0cf5 100644 --- a/examples/stm32wb/src/bin/gatt_server.rs +++ b/examples/stm32wb/src/bin/gatt_server.rs @@ -151,11 +151,6 @@ async fn main(_spawner: Spawner) { let response = mbox.ble_subsystem.read().await; defmt::debug!("{}", response); - info!("set scan response data..."); - mbox.ble_subsystem.le_set_scan_response_data(b"TXTX").await.unwrap(); - let response = mbox.ble_subsystem.read().await; - defmt::debug!("{}", response); - defmt::info!("initializing services and characteristics..."); let mut ble_context = init_gatt_services(&mut mbox.ble_subsystem).await.unwrap(); defmt::info!("{}", ble_context); diff --git a/examples/stm32wba/.cargo/config.toml b/examples/stm32wba/.cargo/config.toml index 477413397..c96a5cb6c 100644 --- a/examples/stm32wba/.cargo/config.toml +++ b/examples/stm32wba/.cargo/config.toml @@ -1,5 +1,5 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-rs run --chip STM32WBA52CGUxT" +runner = "probe-rs run --chip STM32WBA55CGUx" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/stm32wba/Cargo.toml b/examples/stm32wba/Cargo.toml index 401281c0b..2c638f9f4 100644 --- a/examples/stm32wba/Cargo.toml +++ b/examples/stm32wba/Cargo.toml @@ -5,19 +5,19 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wba52cg", "time-driver-any", "memory-x", "exti"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wba55cg", "time-driver-any", "memory-x", "exti"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional = true } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" -embedded-hal = "0.2.6" -panic-probe = { version = "0.3", features = ["print-defmt"] } +embedded-hal = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } static_cell = "2" diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 46af5218c..5ecd77443 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -6,20 +6,20 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32wl55jc-cm4 to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-storage = "0.3.1" -panic-probe = { version = "0.3", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } heapless = { version = "0.8", default-features = false } chrono = { version = "^0.4", default-features = false } diff --git a/examples/stm32wl/memory.x b/examples/stm32wl/memory.x index 0298caa4b..4590867a8 100644 --- a/examples/stm32wl/memory.x +++ b/examples/stm32wl/memory.x @@ -2,8 +2,8 @@ MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ FLASH : ORIGIN = 0x08000000, LENGTH = 256K - SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64 - RAM (rwx) : ORIGIN = 0x20000040, LENGTH = 64K - 64 + SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128 + RAM (rwx) : ORIGIN = 0x20000080, LENGTH = 64K - 128 } SECTIONS @@ -12,4 +12,4 @@ SECTIONS { *(.shared_data) } > SHARED_RAM -} \ No newline at end of file +} diff --git a/examples/stm32wl/src/bin/blinky.rs b/examples/stm32wl/src/bin/blinky.rs index ce7d0ec58..a2a90871d 100644 --- a/examples/stm32wl/src/bin/blinky.rs +++ b/examples/stm32wl/src/bin/blinky.rs @@ -10,7 +10,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/button.rs b/examples/stm32wl/src/bin/button.rs index 8b5204479..21bcd2ac6 100644 --- a/examples/stm32wl/src/bin/button.rs +++ b/examples/stm32wl/src/bin/button.rs @@ -9,7 +9,7 @@ use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[entry] diff --git a/examples/stm32wl/src/bin/button_exti.rs b/examples/stm32wl/src/bin/button_exti.rs index 8dd1a6a5e..0a8aece34 100644 --- a/examples/stm32wl/src/bin/button_exti.rs +++ b/examples/stm32wl/src/bin/button_exti.rs @@ -10,7 +10,7 @@ use embassy_stm32::gpio::Pull; use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/flash.rs b/examples/stm32wl/src/bin/flash.rs index 147f5d293..320a9723a 100644 --- a/examples/stm32wl/src/bin/flash.rs +++ b/examples/stm32wl/src/bin/flash.rs @@ -9,7 +9,7 @@ use embassy_stm32::flash::Flash; use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/random.rs b/examples/stm32wl/src/bin/random.rs index df2ed0054..68b9d7d00 100644 --- a/examples/stm32wl/src/bin/random.rs +++ b/examples/stm32wl/src/bin/random.rs @@ -14,7 +14,7 @@ bind_interrupts!(struct Irqs{ RNG => rng::InterruptHandler; }); -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/rtc.rs b/examples/stm32wl/src/bin/rtc.rs index 69a9ddc4c..d3709120f 100644 --- a/examples/stm32wl/src/bin/rtc.rs +++ b/examples/stm32wl/src/bin/rtc.rs @@ -12,7 +12,7 @@ use embassy_stm32::{Config, SharedData}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/uart_async.rs b/examples/stm32wl/src/bin/uart_async.rs index ece9b9201..505a85f47 100644 --- a/examples/stm32wl/src/bin/uart_async.rs +++ b/examples/stm32wl/src/bin/uart_async.rs @@ -14,7 +14,7 @@ bind_interrupts!(struct Irqs{ LPUART1 => InterruptHandler; }); -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); /* diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index 9bd37550c..9e553f52b 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -8,15 +8,14 @@ license = "MIT OR Apache-2.0" crate-type = ["cdylib"] [dependencies] -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "wasm", ] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["log", "wasm", ] } wasm-logger = "0.2.0" wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] } log = "0.4.11" -critical-section = { version = "1.1", features = ["std"] } [profile.release] debug = 2 diff --git a/rust-toolchain-nightly.toml b/rust-toolchain-nightly.toml index dfa231344..e75ea40cc 100644 --- a/rust-toolchain-nightly.toml +++ b/rust-toolchain-nightly.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-07-16" +channel = "nightly-2025-03-12" components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] targets = [ "thumbv7em-none-eabi", @@ -9,4 +9,5 @@ targets = [ "thumbv8m.main-none-eabihf", "riscv32imac-unknown-none-elf", "wasm32-unknown-unknown", + "armv7a-none-eabi", ] diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ce9040a70..870904c3a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.80" +channel = "1.85" components = [ "rust-src", "rustfmt", "llvm-tools" ] targets = [ "thumbv7em-none-eabi", @@ -9,4 +9,7 @@ targets = [ "thumbv8m.main-none-eabihf", "riscv32imac-unknown-none-elf", "wasm32-unknown-unknown", + "armv7a-none-eabi", + "armv7r-none-eabi", + "armv7r-none-eabihf", ] diff --git a/rustfmt.toml b/rustfmt.toml index 3639f4386..2561562fd 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,4 @@ group_imports = "StdExternalCrate" imports_granularity = "Module" -max_width=120 \ No newline at end of file +edition = "2021" +max_width = 120 diff --git a/tests/mspm0/.cargo/config.toml b/tests/mspm0/.cargo/config.toml new file mode 100644 index 000000000..825bf3ae9 --- /dev/null +++ b/tests/mspm0/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "teleprobe local run --chip MSPM0G3507 --protocol swd --elf" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace,embassy_hal_internal=debug" diff --git a/tests/mspm0/Cargo.toml b/tests/mspm0/Cargo.toml new file mode 100644 index 000000000..298522d09 --- /dev/null +++ b/tests/mspm0/Cargo.toml @@ -0,0 +1,58 @@ +[package] +edition = "2021" +name = "embassy-mspm0-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[features] +mspm0g3507 = [ "embassy-mspm0/mspm0g3507pm" ] + +[dependencies] +teleprobe-meta = "1.1" + +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = [ "defmt" ] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = [ "arch-cortex-m", "executor-thread", "defmt" ] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = [ "defmt" ] } +embassy-mspm0 = { version = "0.1.0", path = "../../embassy-mspm0", features = [ "rt", "defmt", "unstable-pac", "time-driver-any" ] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal/"} + +defmt = "1.0.1" +defmt-rtt = "1.0.0" + +cortex-m = { version = "0.7.6", features = [ "inline-asm", "critical-section-single-core" ]} +cortex-m-rt = "0.7.0" +embedded-hal = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +static_cell = "2" +portable-atomic = { version = "1.5", features = ["critical-section"] } + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/tests/mspm0/build.rs b/tests/mspm0/build.rs new file mode 100644 index 000000000..0b58fb9e9 --- /dev/null +++ b/tests/mspm0/build.rs @@ -0,0 +1,26 @@ +use std::error::Error; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() -> Result<(), Box> { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + + #[cfg(feature = "mspm0g3507")] + let memory_x = include_bytes!("memory_g3507.x"); + + fs::write(out.join("memory.x"), memory_x).unwrap(); + + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=link_ram.x"); + // copy main linker script. + fs::write(out.join("link_ram.x"), include_bytes!("../link_ram_cortex_m.x")).unwrap(); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); + // You must tell cargo to link interrupt groups if the rt feature is enabled. + println!("cargo:rustc-link-arg-bins=-Tinterrupt_group.x"); + + Ok(()) +} diff --git a/tests/mspm0/memory_g3507.x b/tests/mspm0/memory_g3507.x new file mode 100644 index 000000000..37e381fbd --- /dev/null +++ b/tests/mspm0/memory_g3507.x @@ -0,0 +1,6 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 128K + /* Select non-parity range of SRAM due to SRAM_ERR_01 errata in SLAZ758 */ + RAM : ORIGIN = 0x20200000, LENGTH = 32K +} diff --git a/tests/mspm0/src/bin/uart.rs b/tests/mspm0/src/bin/uart.rs new file mode 100644 index 000000000..458129d44 --- /dev/null +++ b/tests/mspm0/src/bin/uart.rs @@ -0,0 +1,83 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "mspm0g3507")] +teleprobe_meta::target!(b"lp-mspm0g3507"); + +use defmt::{assert_eq, unwrap, *}; +use embassy_executor::Spawner; +use embassy_mspm0::mode::Blocking; +use embassy_mspm0::uart::{ClockSel, Config, Error, Uart}; +use {defmt_rtt as _, panic_probe as _}; + +fn read(uart: &mut Uart<'_, Blocking>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.blocking_read(&mut buf)?; + Ok(buf) +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_mspm0::init(Default::default()); + info!("Hello World!"); + + // TODO: Allow creating a looped-back UART (so pins are not needed). + // Do not select default UART since the virtual COM port is attached to UART0. + #[cfg(feature = "mspm0g3507")] + let (mut tx, mut rx, mut uart) = (p.PA8, p.PA9, p.UART1); + + const MFCLK_BUAD_RATES: &[u32] = &[1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200]; + + for &rate in MFCLK_BUAD_RATES { + info!("{} baud using MFCLK", rate); + + let mut config = Config::default(); + // MSPM0 hardware supports a loopback mode to allow self test. + config.loop_back_enable = true; + config.baudrate = rate; + + let mut uart = unwrap!(Uart::new_blocking( + uart.reborrow(), + rx.reborrow(), + tx.reborrow(), + config + )); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + + let data = [0xC0, 0xDE]; + unwrap!(uart.blocking_write(&data)); + assert_eq!(unwrap!(read(&mut uart)), data); + } + + // 9600 is the maximum possible value for 32.768 kHz. + const LFCLK_BAUD_RATES: &[u32] = &[1200, 2400, 4800, 9600]; + + for &rate in LFCLK_BAUD_RATES { + info!("{} baud using LFCLK", rate); + + let mut config = Config::default(); + // MSPM0 hardware supports a loopback mode to allow self test. + config.loop_back_enable = true; + config.baudrate = rate; + config.clock_source = ClockSel::LfClk; + + let mut uart = expect!(Uart::new_blocking( + uart.reborrow(), + rx.reborrow(), + tx.reborrow(), + config, + )); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + + let data = [0xC0, 0xDE]; + unwrap!(uart.blocking_write(&data)); + assert_eq!(unwrap!(read(&mut uart)), data); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml index f666634d5..30c4223b7 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -8,25 +8,25 @@ license = "MIT OR Apache-2.0" teleprobe-meta = "1" embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt", ] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt", ] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.3.1", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } -embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } -embassy-net-enc28j60 = { version = "0.1.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } +embassy-net-esp-hosted = { version = "0.2.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embassy-net-enc28j60 = { version = "0.2.0", path = "../../embassy-net-enc28j60", features = ["defmt"] } embedded-hal-async = { version = "1.0" } embedded-hal-bus = { version = "0.1", features = ["async"] } static_cell = "2" perf-client = { path = "../perf-client" } -defmt = "0.3" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" 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"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } portable-atomic = { version = "1.6.0" } [features] @@ -86,6 +86,11 @@ name = "gpiote" path = "src/bin/gpiote.rs" required-features = [] +[[bin]] +name = "spim" +path = "src/bin/spim.rs" +required-features = [ "easydma",] + [[bin]] name = "timer" path = "src/bin/timer.rs" diff --git a/tests/nrf/src/bin/buffered_uart.rs b/tests/nrf/src/bin/buffered_uart.rs index 04f32832f..8c4827464 100644 --- a/tests/nrf/src/bin/buffered_uart.rs +++ b/tests/nrf/src/bin/buffered_uart.rs @@ -25,14 +25,14 @@ async fn main(_spawner: Spawner) { // test teardown + recreate of the buffereduarte works fine. for _ in 0..2 { let u = BufferedUarte::new( - &mut peri!(p, UART0), - &mut p.TIMER0, - &mut p.PPI_CH0, - &mut p.PPI_CH1, - &mut p.PPI_GROUP0, + peri!(p, UART0).reborrow(), + p.TIMER0.reborrow(), + p.PPI_CH0.reborrow(), + p.PPI_CH1.reborrow(), + p.PPI_GROUP0.reborrow(), + peri!(p, PIN_A).reborrow(), + peri!(p, PIN_B).reborrow(), irqs!(UART0_BUFFERED), - &mut peri!(p, PIN_A), - &mut peri!(p, PIN_B), config.clone(), &mut rx_buffer, &mut tx_buffer, diff --git a/tests/nrf/src/bin/buffered_uart_full.rs b/tests/nrf/src/bin/buffered_uart_full.rs index 09353bbe8..e0f41b891 100644 --- a/tests/nrf/src/bin/buffered_uart_full.rs +++ b/tests/nrf/src/bin/buffered_uart_full.rs @@ -28,9 +28,9 @@ async fn main(_spawner: Spawner) { p.PPI_CH0, p.PPI_CH1, p.PPI_GROUP0, - irqs!(UART0_BUFFERED), peri!(p, PIN_A), peri!(p, PIN_B), + irqs!(UART0_BUFFERED), config.clone(), &mut rx_buffer, &mut tx_buffer, diff --git a/tests/nrf/src/bin/buffered_uart_halves.rs b/tests/nrf/src/bin/buffered_uart_halves.rs index bdf5ad726..e6debd76e 100644 --- a/tests/nrf/src/bin/buffered_uart_halves.rs +++ b/tests/nrf/src/bin/buffered_uart_halves.rs @@ -27,21 +27,21 @@ async fn main(_spawner: Spawner) { const COUNT: usize = 40_000; let mut tx = BufferedUarteTx::new( - &mut peri!(p, UART1), + peri!(p, UART1).reborrow(), + peri!(p, PIN_A).reborrow(), irqs!(UART1_BUFFERED), - &mut peri!(p, PIN_A), config.clone(), &mut tx_buffer, ); let mut rx = BufferedUarteRx::new( - &mut peri!(p, UART0), - &mut p.TIMER0, - &mut p.PPI_CH0, - &mut p.PPI_CH1, - &mut p.PPI_GROUP0, + peri!(p, UART0).reborrow(), + p.TIMER0.reborrow(), + p.PPI_CH0.reborrow(), + p.PPI_CH1.reborrow(), + p.PPI_GROUP0.reborrow(), irqs!(UART0_BUFFERED), - &mut peri!(p, PIN_B), + peri!(p, PIN_B).reborrow(), config.clone(), &mut rx_buffer, ); diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs index e8fca452e..24ddd06f3 100644 --- a/tests/nrf/src/bin/buffered_uart_spam.rs +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -27,7 +27,11 @@ async fn main(_spawner: Spawner) { let mut rx_buffer = [0u8; 1024]; - mem::forget(Output::new(&mut peri!(p, PIN_A), Level::High, OutputDrive::Standard)); + mem::forget(Output::new( + peri!(p, PIN_A).reborrow(), + Level::High, + OutputDrive::Standard, + )); let mut u = BufferedUarteRx::new( peri!(p, UART0), @@ -50,15 +54,15 @@ async fn main(_spawner: Spawner) { const NSPAM: usize = 17; static mut TX_BUF: [u8; NSPAM] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; let _spam = UarteTx::new(peri!(p, UART1), irqs!(UART1), peri!(p, PIN_A), config.clone()); - let spam_peri: pac::UARTE1 = unsafe { mem::transmute(()) }; - let event = unsafe { Event::new_unchecked(NonNull::new_unchecked(&spam_peri.events_endtx as *const _ as _)) }; - let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(&spam_peri.tasks_starttx as *const _ as _)) }; + let spam_peri = pac::UARTE1; + let event = unsafe { Event::new_unchecked(NonNull::new_unchecked(spam_peri.events_endtx().as_ptr())) }; + let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(spam_peri.tasks_starttx().as_ptr())) }; let mut spam_ppi = Ppi::new_one_to_one(p.PPI_CH2, event, task); spam_ppi.enable(); - let p = unsafe { TX_BUF.as_mut_ptr() }; - spam_peri.txd.ptr.write(|w| unsafe { w.ptr().bits(p as u32) }); - spam_peri.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(NSPAM as _) }); - spam_peri.tasks_starttx.write(|w| unsafe { w.bits(1) }); + let p = (&raw mut TX_BUF) as *mut u8; + spam_peri.txd().ptr().write_value(p as u32); + spam_peri.txd().maxcnt().write(|w| w.set_maxcnt(NSPAM as _)); + spam_peri.tasks_starttx().write_value(1); let mut i = 0; let mut total = 0; diff --git a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs index 304754c0e..ed58627f1 100644 --- a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs +++ b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs @@ -6,7 +6,7 @@ teleprobe_meta::timeout!(120); use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_enc28j60::Enc28j60; use embassy_nrf::gpio::{Level, Output, OutputDrive}; use embassy_nrf::rng::Rng; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type MyDriver = Enc28j60, Output<'static>, Delay>, Output<'static>>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -65,11 +65,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/nrf/src/bin/gpio.rs b/tests/nrf/src/bin/gpio.rs index 9e809a694..4995d244c 100644 --- a/tests/nrf/src/bin/gpio.rs +++ b/tests/nrf/src/bin/gpio.rs @@ -17,10 +17,12 @@ async fn main(_spawner: Spawner) { let mut output = Output::new(peri!(p, PIN_B), Level::Low, OutputDrive::Standard); output.set_low(); + assert!(output.is_set_low()); Timer::after_millis(10).await; assert!(input.is_low()); output.set_high(); + assert!(output.is_set_high()); Timer::after_millis(10).await; assert!(input.is_high()); diff --git a/tests/nrf/src/bin/spim.rs b/tests/nrf/src/bin/spim.rs new file mode 100644 index 000000000..2b38f0409 --- /dev/null +++ b/tests/nrf/src/bin/spim.rs @@ -0,0 +1,42 @@ +// required-features: easydma +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_nrf::spim::Spim; +use embassy_nrf::{peripherals, spim}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M1; + let mut spim = Spim::new( + peri!(p, SPIM0).reborrow(), + irqs!(SPIM0), + peri!(p, PIN_X).reborrow(), + peri!(p, PIN_A).reborrow(), // MISO + peri!(p, PIN_B).reborrow(), // MOSI + config.clone(), + ); + let data = [ + 0x42, 0x43, 0x44, 0x45, 0x66, 0x12, 0x23, 0x34, 0x45, 0x19, 0x91, 0xaa, 0xff, 0xa5, 0x5a, 0x77, + ]; + let mut buf = [0u8; 16]; + + buf.fill(0); + spim.blocking_transfer(&mut buf, &data).unwrap(); + assert_eq!(data, buf); + + buf.fill(0); + spim.transfer(&mut buf, &data).await.unwrap(); + assert_eq!(data, buf); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/src/bin/uart_halves.rs b/tests/nrf/src/bin/uart_halves.rs index f48ea43a1..a462f80ce 100644 --- a/tests/nrf/src/bin/uart_halves.rs +++ b/tests/nrf/src/bin/uart_halves.rs @@ -19,8 +19,18 @@ async fn main(_spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD1M; - let mut tx = UarteTx::new(&mut peri!(p, UART0), irqs!(UART0), &mut peri!(p, PIN_A), config.clone()); - let mut rx = UarteRx::new(&mut peri!(p, UART1), irqs!(UART1), &mut peri!(p, PIN_B), config.clone()); + let mut tx = UarteTx::new( + peri!(p, UART0).reborrow(), + irqs!(UART0), + peri!(p, PIN_A).reborrow(), + config.clone(), + ); + let mut rx = UarteRx::new( + peri!(p, UART1).reborrow(), + irqs!(UART1), + peri!(p, PIN_B).reborrow(), + config.clone(), + ); let data = [ 0x42, 0x43, 0x44, 0x45, 0x66, 0x12, 0x23, 0x34, 0x45, 0x19, 0x91, 0xaa, 0xff, 0xa5, 0x5a, 0x77, diff --git a/tests/nrf/src/bin/uart_split.rs b/tests/nrf/src/bin/uart_split.rs index 70d8b2e33..8fe710068 100644 --- a/tests/nrf/src/bin/uart_split.rs +++ b/tests/nrf/src/bin/uart_split.rs @@ -21,10 +21,10 @@ async fn main(_spawner: Spawner) { config.baudrate = uarte::Baudrate::BAUD9600; let uarte = Uarte::new( - &mut peri!(p, UART0), + peri!(p, UART0).reborrow(), + peri!(p, PIN_A).reborrow(), + peri!(p, PIN_B).reborrow(), irqs!(UART0), - &mut peri!(p, PIN_A), - &mut peri!(p, PIN_B), config.clone(), ); let (mut tx, mut rx) = uarte.split(); diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs index 6632442f1..34fb8103b 100644 --- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs +++ b/tests/nrf/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_net::{Config, StackResources}; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; @@ -40,8 +40,8 @@ async fn wifi_task( type MyDriver = hosted::NetDriver<'static>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -86,16 +86,15 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/nrf/src/common.rs b/tests/nrf/src/common.rs index ff5299b0f..ebd332d15 100644 --- a/tests/nrf/src/common.rs +++ b/tests/nrf/src/common.rs @@ -52,51 +52,66 @@ define_peris!(PIN_A = P0_13, PIN_B = P0_14,); #[cfg(feature = "nrf52832")] define_peris!( PIN_A = P0_11, PIN_B = P0_12, + PIN_X = P0_13, UART0 = UARTE0, - @irq UART0 = {UARTE0_UART0 => uarte::InterruptHandler;}, - @irq UART0_BUFFERED = {UARTE0_UART0 => buffered_uarte::InterruptHandler;}, + SPIM0 = TWISPI0, + @irq UART0 = {UARTE0 => uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {UARTE0 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {TWISPI0 => spim::InterruptHandler;}, ); #[cfg(feature = "nrf52833")] define_peris!( PIN_A = P1_01, PIN_B = P1_02, + PIN_X = P1_03, UART0 = UARTE0, UART1 = UARTE1, - @irq UART0 = {UARTE0_UART0 => uarte::InterruptHandler;}, + SPIM0 = TWISPI0, + @irq UART0 = {UARTE0 => uarte::InterruptHandler;}, @irq UART1 = {UARTE1 => uarte::InterruptHandler;}, - @irq UART0_BUFFERED = {UARTE0_UART0 => buffered_uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {UARTE0 => buffered_uarte::InterruptHandler;}, @irq UART1_BUFFERED = {UARTE1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {TWISPI0 => spim::InterruptHandler;}, ); #[cfg(feature = "nrf52840")] define_peris!( PIN_A = P1_02, PIN_B = P1_03, + PIN_X = P1_04, UART0 = UARTE0, UART1 = UARTE1, - @irq UART0 = {UARTE0_UART0 => uarte::InterruptHandler;}, + SPIM0 = TWISPI0, + @irq UART0 = {UARTE0 => uarte::InterruptHandler;}, @irq UART1 = {UARTE1 => uarte::InterruptHandler;}, - @irq UART0_BUFFERED = {UARTE0_UART0 => buffered_uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {UARTE0 => buffered_uarte::InterruptHandler;}, @irq UART1_BUFFERED = {UARTE1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {TWISPI0 => spim::InterruptHandler;}, ); #[cfg(feature = "nrf5340")] define_peris!( PIN_A = P1_08, PIN_B = P1_09, + PIN_X = P1_10, UART0 = SERIAL0, UART1 = SERIAL1, + SPIM0 = SERIAL0, @irq UART0 = {SERIAL0 => uarte::InterruptHandler;}, @irq UART1 = {SERIAL1 => uarte::InterruptHandler;}, @irq UART0_BUFFERED = {SERIAL0 => buffered_uarte::InterruptHandler;}, @irq UART1_BUFFERED = {SERIAL1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {SERIAL0 => spim::InterruptHandler;}, ); #[cfg(feature = "nrf9160")] define_peris!( PIN_A = P0_00, PIN_B = P0_01, + PIN_X = P0_02, UART0 = SERIAL0, UART1 = SERIAL1, - @irq UART0 = {UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => uarte::InterruptHandler;}, - @irq UART1 = {UARTE1_SPIM1_SPIS1_TWIM1_TWIS1 => uarte::InterruptHandler;}, - @irq UART0_BUFFERED = {UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => buffered_uarte::InterruptHandler;}, - @irq UART1_BUFFERED = {UARTE1_SPIM1_SPIS1_TWIM1_TWIS1 => buffered_uarte::InterruptHandler;}, + SPIM0 = SERIAL0, + @irq UART0 = {SERIAL0 => uarte::InterruptHandler;}, + @irq UART1 = {SERIAL1 => uarte::InterruptHandler;}, + @irq UART0_BUFFERED = {SERIAL0 => buffered_uarte::InterruptHandler;}, + @irq UART1_BUFFERED = {SERIAL1 => buffered_uarte::InterruptHandler;}, + @irq SPIM0 = {SERIAL0 => spim::InterruptHandler;}, ); diff --git a/tests/perf-client/Cargo.toml b/tests/perf-client/Cargo.toml index eb2a33a30..e31d6361b 100644 --- a/tests/perf-client/Cargo.toml +++ b/tests/perf-client/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", ] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -defmt = "0.3.0" +defmt = "1.0.1" diff --git a/tests/perf-client/src/lib.rs b/tests/perf-client/src/lib.rs index 54762379a..4bd9e5674 100644 --- a/tests/perf-client/src/lib.rs +++ b/tests/perf-client/src/lib.rs @@ -2,7 +2,6 @@ use defmt::{assert, *}; use embassy_futures::join::join; -use embassy_net::driver::Driver; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack}; use embassy_time::{with_timeout, Duration, Timer}; @@ -13,7 +12,7 @@ pub struct Expected { pub updown_kbps: usize, } -pub async fn run(stack: &Stack, expected: Expected) { +pub async fn run(stack: Stack<'_>, expected: Expected) { info!("Waiting for DHCP up..."); while stack.config_v4().is_none() { Timer::after_millis(100).await; @@ -38,7 +37,7 @@ const DOWNLOAD_PORT: u16 = 4321; const UPLOAD_PORT: u16 = 4322; const UPLOAD_DOWNLOAD_PORT: u16 = 4323; -async fn test_download(stack: &Stack) -> usize { +async fn test_download(stack: Stack<'_>) -> usize { info!("Testing download..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; @@ -78,7 +77,7 @@ async fn test_download(stack: &Stack) -> usize { kbps } -async fn test_upload(stack: &Stack) -> usize { +async fn test_upload(stack: Stack<'_>) -> usize { info!("Testing upload..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; @@ -118,7 +117,7 @@ async fn test_upload(stack: &Stack) -> usize { kbps } -async fn test_upload_download(stack: &Stack) -> usize { +async fn test_upload_download(stack: Stack<'_>) -> usize { info!("Testing upload+download..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; diff --git a/tests/riscv32/Cargo.toml b/tests/riscv32/Cargo.toml index ae2b0e180..c5f6a1194 100644 --- a/tests/riscv32/Cargo.toml +++ b/tests/riscv32/Cargo.toml @@ -6,9 +6,9 @@ license = "MIT OR Apache-2.0" [dependencies] critical-section = { version = "1.1.1", features = ["restore-state-bool"] } -embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time" } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync" } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } riscv-rt = "0.12.2" diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml index de7bb0e56..649c15048 100644 --- a/tests/rp/.cargo/config.toml +++ b/tests/rp/.cargo/config.toml @@ -5,18 +5,19 @@ #build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "teleprobe client run" +#runner = "teleprobe client run" #runner = "teleprobe local run --chip RP2040 --elf" +runner = "teleprobe local run --chip RP235X --elf" rustflags = [ # Code-size optimizations. #"-Z", "trap-unreachable=no", - "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] [build] -target = "thumbv6m-none-eabi" +#target = "thumbv6m-none-eabi" +target = "thumbv8m.main-none-eabihf" [env] DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info" diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 12f1ec3ce..2be37f525 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -4,23 +4,28 @@ name = "embassy-rp-tests" version = "0.1.0" license = "MIT OR Apache-2.0" +[features] +rp2040 = ["embassy-rp/rp2040"] +rp235xa = ["embassy-rp/rp235xa"] +rp235xb = ["embassy-rp/rp235xb"] + [dependencies] teleprobe-meta = "1.1" -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } -embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram", "rp2040"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", ] } +embassy-rp = { version = "0.4.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-net = { version = "0.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.2.0", path = "../../embassy-embedded-hal/"} +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-net-wiznet = { version = "0.2.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-embedded-hal = { version = "0.3.0", path = "../../embassy-embedded-hal/"} cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } -cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt"] } perf-client = { path = "../perf-client" } -defmt = "0.3.0" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6" } cortex-m-rt = "0.7.0" @@ -28,14 +33,35 @@ embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-bus = { version = "0.1", features = ["async"] } -panic-probe = { version = "0.3.0", features = ["print-defmt"] } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } embedded-io-async = { version = "0.6.1" } embedded-storage = { version = "0.3" } static_cell = "2" portable-atomic = { version = "1.5", features = ["critical-section"] } -pio = "0.2" -pio-proc = "0.2" -rand = { version = "0.8.5", default-features = false } + +# bootsel not currently supported on 2350 +[[bin]] +name = "bootsel" +path = "src/bin/bootsel.rs" +required-features = [ "rp2040",] + +# 2350 devboard isn't a W +[[bin]] +name = "cyw43-perf" +path = "src/bin/cyw43-perf.rs" +required-features = [ "rp2040",] + +# Eth test only for the w5100s-evb-pico +[[bin]] +name = "ethernet_w5100s_perf" +path = "src/bin/ethernet_w5100s_perf.rs" +required-features = [ "rp2040",] + +# Float intrinsics are only relevant for the 2040 +[[bin]] +name = "float" +path = "src/bin/float.rs" +required-features = [ "rp2040",] [profile.dev] debug = 2 diff --git a/tests/rp/readme.md b/tests/rp/readme.md new file mode 100644 index 000000000..f8192a95a --- /dev/null +++ b/tests/rp/readme.md @@ -0,0 +1,8 @@ +# Pico and Pico 2 Plus connections + +GP0-GP1 +GP3-GP4 +GP6-GP9 +GP7-GP11 +GP18-GP20 with 10k pullup +GP19-GP21 with 10k pullup diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs index 65c246472..c2175bc03 100644 --- a/tests/rp/src/bin/adc.rs +++ b/tests/rp/src/bin/adc.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::*; use embassy_executor::Spawner; @@ -20,14 +23,19 @@ async fn main(_spawner: Spawner) { let _wifi_off = Output::new(p.PIN_25, Level::High); let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + #[cfg(any(feature = "rp2040", feature = "rp235xa"))] + let (mut a, mut b, mut c, mut d) = (p.PIN_26, p.PIN_27, p.PIN_28, p.PIN_29); + #[cfg(feature = "rp235xb")] + let (mut a, mut b, mut c, mut d) = (p.PIN_44, p.PIN_45, p.PIN_46, p.PIN_47); + { { - let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Down); + let mut p = Channel::new_pin(a.reborrow(), Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); defmt::assert!(adc.read(&mut p).await.unwrap() < 0b01_0000_0000); } { - let mut p = Channel::new_pin(&mut p.PIN_26, Pull::Up); + let mut p = Channel::new_pin(a.reborrow(), Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); defmt::assert!(adc.read(&mut p).await.unwrap() > 0b11_0000_0000); } @@ -35,21 +43,21 @@ async fn main(_spawner: Spawner) { // not bothering with async reads from now on { { - let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Down); + let mut p = Channel::new_pin(b.reborrow(), Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); } { - let mut p = Channel::new_pin(&mut p.PIN_27, Pull::Up); + let mut p = Channel::new_pin(b.reborrow(), Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); } } { { - let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Down); + let mut p = Channel::new_pin(c.reborrow(), Pull::Down); defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); } { - let mut p = Channel::new_pin(&mut p.PIN_28, Pull::Up); + let mut p = Channel::new_pin(c.reborrow(), Pull::Up); defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); } } @@ -57,15 +65,15 @@ async fn main(_spawner: Spawner) { // gp29 is connected to vsys through a 200k/100k divider, // adding pulls should change the value let low = { - let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Down); + let mut p = Channel::new_pin(d.reborrow(), Pull::Down); adc.blocking_read(&mut p).unwrap() }; let none = { - let mut p = Channel::new_pin(&mut p.PIN_29, Pull::None); + let mut p = Channel::new_pin(d.reborrow(), Pull::None); adc.blocking_read(&mut p).unwrap() }; let up = { - let mut p = Channel::new_pin(&mut p.PIN_29, Pull::Up); + let mut p = Channel::new_pin(d.reborrow(), Pull::Up); adc.blocking_read(&mut p).unwrap() }; defmt::assert!(low < none); @@ -73,7 +81,7 @@ async fn main(_spawner: Spawner) { } { let temp = convert_to_celsius( - adc.read(&mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR)) + adc.read(&mut Channel::new_temp_sensor(p.ADC_TEMP_SENSOR.reborrow())) .await .unwrap(), ); @@ -90,26 +98,26 @@ async fn main(_spawner: Spawner) { let mut none = [0u8; 16]; let mut up = [Sample::default(); 16]; adc.read_many( - &mut Channel::new_pin(&mut p.PIN_29, Pull::Down), + &mut Channel::new_pin(d.reborrow(), Pull::Down), &mut low, 1, - &mut p.DMA_CH0, + p.DMA_CH0.reborrow(), ) .await .unwrap(); adc.read_many( - &mut Channel::new_pin(&mut p.PIN_29, Pull::None), + &mut Channel::new_pin(d.reborrow(), Pull::None), &mut none, 1, - &mut p.DMA_CH0, + p.DMA_CH0.reborrow(), ) .await .unwrap(); adc.read_many_raw( - &mut Channel::new_pin(&mut p.PIN_29, Pull::Up), + &mut Channel::new_pin(d.reborrow(), Pull::Up), &mut up, 1, - &mut p.DMA_CH0, + p.DMA_CH0.reborrow(), ) .await; defmt::assert!(low.iter().zip(none.iter()).all(|(l, n)| *l >> 4 < *n as u16)); @@ -119,10 +127,10 @@ async fn main(_spawner: Spawner) { { let mut temp = [0u16; 16]; adc.read_many( - &mut Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), + &mut Channel::new_temp_sensor(p.ADC_TEMP_SENSOR.reborrow()), &mut temp, 1, - &mut p.DMA_CH0, + p.DMA_CH0.reborrow(), ) .await .unwrap(); @@ -133,10 +141,10 @@ async fn main(_spawner: Spawner) { { let mut multi = [0u16; 2]; let mut channels = [ - Channel::new_pin(&mut p.PIN_26, Pull::Up), - Channel::new_temp_sensor(&mut p.ADC_TEMP_SENSOR), + Channel::new_pin(a.reborrow(), Pull::Up), + Channel::new_temp_sensor(p.ADC_TEMP_SENSOR.reborrow()), ]; - adc.read_many_multichannel(&mut channels, &mut multi, 1, &mut p.DMA_CH0) + adc.read_many_multichannel(&mut channels, &mut multi, 1, p.DMA_CH0.reborrow()) .await .unwrap(); defmt::assert!(multi[0] > 3_000); diff --git a/tests/rp/src/bin/bootsel.rs b/tests/rp/src/bin/bootsel.rs index e88d8bf6c..aa123ab03 100644 --- a/tests/rp/src/bin/bootsel.rs +++ b/tests/rp/src/bin/bootsel.rs @@ -4,12 +4,13 @@ teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; +use embassy_rp::bootsel::is_bootsel_pressed; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let mut p = embassy_rp::init(Default::default()); + let p = embassy_rp::init(Default::default()); info!("Hello World!"); // add some delay to give an attached debug probe time to parse the @@ -18,7 +19,7 @@ async fn main(_spawner: Spawner) { // https://github.com/knurling-rs/defmt/pull/683 Timer::after_millis(10).await; - assert_eq!(p.BOOTSEL.is_pressed(), false); + assert_eq!(is_bootsel_pressed(p.BOOTSEL), false); info!("Test OK"); cortex_m::asm::bkpt(); diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs index 38fbde7c1..dba1058a8 100644 --- a/tests/rp/src/bin/cyw43-perf.rs +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -2,10 +2,11 @@ #![no_main] teleprobe_meta::target!(b"rpi-pico"); -use cyw43_pio::PioSpi; +use cyw43::JoinOptions; +use cyw43_pio::{PioSpi, DEFAULT_CLOCK_DIVIDER}; use defmt::{panic, *}; use embassy_executor::Spawner; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; @@ -20,7 +21,7 @@ bind_interrupts!(struct Irqs { teleprobe_meta::timeout!(120); // Test-only wifi network, no internet access! -const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_NETWORK: &str = "EmbassyTestWPA2"; const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; #[embassy_executor::task] @@ -29,8 +30,8 @@ async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stati } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -46,14 +47,25 @@ async fn main(spawner: Spawner) { // cyw43 firmware needs to be flashed manually: // probe-rs download 43439A0.bin --binary-format bin --chip RP2040 --base-address 0x101b0000 + // probe-rs download 43439A0_btfw.bin --binary-format bin --chip RP2040 --base-address 0x101f0000 // probe-rs download 43439A0_clm.bin --binary-format bin --chip RP2040 --base-address 0x101f8000 - let fw = unsafe { core::slice::from_raw_parts(0x101b0000 as *const u8, 230321) }; - let clm = unsafe { core::slice::from_raw_parts(0x101f8000 as *const u8, 4752) }; + let fw = unsafe { core::slice::from_raw_parts(0x101b0000 as *const u8, 231077) }; + let _btfw = unsafe { core::slice::from_raw_parts(0x101f0000 as *const u8, 6164) }; + let clm = unsafe { core::slice::from_raw_parts(0x101f8000 as *const u8, 984) }; let pwr = Output::new(p.PIN_23, Level::Low); let cs = Output::new(p.PIN_25, Level::High); let mut pio = Pio::new(p.PIO0, Irqs); - let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + p.DMA_CH0, + ); static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); @@ -69,19 +81,21 @@ async fn main(spawner: Spawner) { let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( net_device, Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { panic!("join failed with status={}", err.status); @@ -92,9 +106,9 @@ async fn main(spawner: Spawner) { perf_client::run( stack, perf_client::Expected { - down_kbps: 300, - up_kbps: 300, - updown_kbps: 300, + down_kbps: 200, + up_kbps: 200, + updown_kbps: 200, }, ) .await; diff --git a/tests/rp/src/bin/dma_copy_async.rs b/tests/rp/src/bin/dma_copy_async.rs index 7c64bc396..3dcf4f4d8 100644 --- a/tests/rp/src/bin/dma_copy_async.rs +++ b/tests/rp/src/bin/dma_copy_async.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/ethernet_w5100s_perf.rs b/tests/rp/src/bin/ethernet_w5100s_perf.rs index f15f33743..89e0ad32e 100644 --- a/tests/rp/src/bin/ethernet_w5100s_perf.rs +++ b/tests/rp/src/bin/ethernet_w5100s_perf.rs @@ -5,7 +5,7 @@ teleprobe_meta::timeout!(120); use defmt::*; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_wiznet::chip::W5100S; use embassy_net_wiznet::*; use embassy_rp::clocks::RoscRng; @@ -14,7 +14,6 @@ use embassy_rp::peripherals::SPI0; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::Delay; use embedded_hal_bus::spi::ExclusiveDevice; -use rand::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -32,8 +31,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -67,17 +66,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/rp/src/bin/flash.rs b/tests/rp/src/bin/flash.rs index 310f0d586..548a6ee72 100644 --- a/tests/rp/src/bin/flash.rs +++ b/tests/rp/src/bin/flash.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::*; use embassy_executor::Spawner; @@ -24,13 +27,19 @@ async fn main(_spawner: Spawner) { let mut flash = embassy_rp::flash::Flash::<_, Async, { 2 * 1024 * 1024 }>::new(p.FLASH, p.DMA_CH0); // Get JEDEC id - let jedec = defmt::unwrap!(flash.blocking_jedec_id()); - info!("jedec id: 0x{:x}", jedec); + #[cfg(feature = "rp2040")] + { + let jedec = defmt::unwrap!(flash.blocking_jedec_id()); + info!("jedec id: 0x{:x}", jedec); + } // Get unique id - let mut uid = [0; 8]; - defmt::unwrap!(flash.blocking_unique_id(&mut uid)); - info!("unique id: {:?}", uid); + #[cfg(feature = "rp2040")] + { + let mut uid = [0; 8]; + defmt::unwrap!(flash.blocking_unique_id(&mut uid)); + info!("unique id: {:?}", uid); + } let mut buf = [0u8; ERASE_SIZE]; defmt::unwrap!(flash.blocking_read(ADDR_OFFSET, &mut buf)); diff --git a/tests/rp/src/bin/gpio.rs b/tests/rp/src/bin/gpio.rs index e0c309887..8bd0df8d8 100644 --- a/tests/rp/src/bin/gpio.rs +++ b/tests/rp/src/bin/gpio.rs @@ -1,10 +1,15 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert, *}; use embassy_executor::Spawner; -use embassy_rp::gpio::{Flex, Input, Level, Output, OutputOpenDrain, Pull}; +#[cfg(feature = "rp2040")] +use embassy_rp::gpio::OutputOpenDrain; +use embassy_rp::gpio::{Flex, Input, Level, Output, Pull}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -16,10 +21,10 @@ async fn main(_spawner: Spawner) { // Test initial output { - let b = Input::new(&mut b, Pull::None); + let b = Input::new(b.reborrow(), Pull::None); { - let a = Output::new(&mut a, Level::Low); + let a = Output::new(a.reborrow(), Level::Low); delay(); assert!(b.is_low()); assert!(!b.is_high()); @@ -27,7 +32,7 @@ async fn main(_spawner: Spawner) { assert!(!a.is_set_high()); } { - let mut a = Output::new(&mut a, Level::High); + let mut a = Output::new(a.reborrow(), Level::High); delay(); assert!(!b.is_low()); assert!(b.is_high()); @@ -62,12 +67,46 @@ async fn main(_spawner: Spawner) { } } - // Test input no pull + // Test input inversion { - let b = Input::new(&mut b, Pull::None); + let mut b = Input::new(b.reborrow(), Pull::None); + b.set_inversion(true); // no pull, the status is undefined - let mut a = Output::new(&mut a, Level::Low); + let mut a = Output::new(a.reborrow(), Level::Low); + delay(); + assert!(b.is_high()); + a.set_high(); + delay(); + assert!(b.is_low()); + + b.set_inversion(false); + a.set_inversion(true); + + a.set_low(); + delay(); + assert!(b.is_high()); + + a.set_high(); + delay(); + assert!(b.is_low()); + + b.set_inversion(true); + a.set_high(); + delay(); + assert!(b.is_high()); + + a.set_high(); + delay(); + assert!(b.is_high()); + } + + // Test input no pull + { + let b = Input::new(b.reborrow(), Pull::None); + // no pull, the status is undefined + + let mut a = Output::new(a.reborrow(), Level::Low); delay(); assert!(b.is_low()); a.set_high(); @@ -76,12 +115,13 @@ async fn main(_spawner: Spawner) { } // Test input pulldown + #[cfg(feature = "rp2040")] { - let b = Input::new(&mut b, Pull::Down); + let b = Input::new(b.reborrow(), Pull::Down); delay(); assert!(b.is_low()); - let mut a = Output::new(&mut a, Level::Low); + let mut a = Output::new(a.reborrow(), Level::Low); delay(); assert!(b.is_low()); a.set_high(); @@ -91,11 +131,11 @@ async fn main(_spawner: Spawner) { // Test input pullup { - let b = Input::new(&mut b, Pull::Up); + let b = Input::new(b.reborrow(), Pull::Up); delay(); assert!(b.is_high()); - let mut a = Output::new(&mut a, Level::Low); + let mut a = Output::new(a.reborrow(), Level::Low); delay(); assert!(b.is_low()); a.set_high(); @@ -104,9 +144,10 @@ async fn main(_spawner: Spawner) { } // OUTPUT OPEN DRAIN + #[cfg(feature = "rp2040")] { - let mut b = OutputOpenDrain::new(&mut b, Level::High); - let mut a = Flex::new(&mut a); + let mut b = OutputOpenDrain::new(b.reborrow(), Level::High); + let mut a = Flex::new(a.reborrow()); a.set_as_input(); // When an OutputOpenDrain is high, it doesn't drive the pin. @@ -163,12 +204,12 @@ async fn main(_spawner: Spawner) { // Test initial output { //Flex pin configured as input - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(); { //Flex pin configured as output - let mut a = Flex::new(&mut a); //Flex pin configured as output + let mut a = Flex::new(a.reborrow()); //Flex pin configured as output a.set_low(); // Pin state must be set before configuring the pin, thus we avoid unknown state a.set_as_output(); delay(); @@ -176,7 +217,7 @@ async fn main(_spawner: Spawner) { } { //Flex pin configured as output - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_high(); a.set_as_output(); @@ -187,10 +228,10 @@ async fn main(_spawner: Spawner) { // Test input no pull { - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(); // no pull by default. - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_low(); a.set_as_output(); @@ -202,14 +243,15 @@ async fn main(_spawner: Spawner) { } // Test input pulldown + #[cfg(feature = "rp2040")] { - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(); b.set_pull(Pull::Down); delay(); assert!(b.is_low()); - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_low(); a.set_as_output(); delay(); @@ -221,13 +263,13 @@ async fn main(_spawner: Spawner) { // Test input pullup { - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(); b.set_pull(Pull::Up); delay(); assert!(b.is_high()); - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_high(); a.set_as_output(); delay(); diff --git a/tests/rp/src/bin/gpio_async.rs b/tests/rp/src/bin/gpio_async.rs index 40a304464..72fb0910d 100644 --- a/tests/rp/src/bin/gpio_async.rs +++ b/tests/rp/src/bin/gpio_async.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert, *}; use embassy_executor::Spawner; @@ -19,8 +22,8 @@ async fn main(_spawner: Spawner) { { info!("test wait_for_high"); - let mut output = Output::new(&mut output_pin, Level::Low); - let mut input = Input::new(&mut input_pin, Pull::None); + let mut output = Output::new(output_pin.reborrow(), Level::Low); + let mut input = Input::new(input_pin.reborrow(), Pull::None); assert!(input.is_low(), "input was expected to be low"); @@ -40,8 +43,8 @@ async fn main(_spawner: Spawner) { { info!("test wait_for_low"); - let mut output = Output::new(&mut output_pin, Level::High); - let mut input = Input::new(&mut input_pin, Pull::None); + let mut output = Output::new(output_pin.reborrow(), Level::High); + let mut input = Input::new(input_pin.reborrow(), Pull::None); assert!(input.is_high(), "input was expected to be high"); @@ -60,8 +63,8 @@ async fn main(_spawner: Spawner) { { info!("test wait_for_rising_edge"); - let mut output = Output::new(&mut output_pin, Level::Low); - let mut input = Input::new(&mut input_pin, Pull::None); + let mut output = Output::new(output_pin.reborrow(), Level::Low); + let mut input = Input::new(input_pin.reborrow(), Pull::None); assert!(input.is_low(), "input was expected to be low"); @@ -80,8 +83,8 @@ async fn main(_spawner: Spawner) { { info!("test wait_for_falling_edge"); - let mut output = Output::new(&mut output_pin, Level::High); - let mut input = Input::new(&mut input_pin, Pull::None); + let mut output = Output::new(output_pin.reborrow(), Level::High); + let mut input = Input::new(input_pin.reborrow(), Pull::None); assert!(input.is_high(), "input was expected to be high"); @@ -100,8 +103,8 @@ async fn main(_spawner: Spawner) { { info!("test wait_for_any_edge (falling)"); - let mut output = Output::new(&mut output_pin, Level::High); - let mut input = Input::new(&mut input_pin, Pull::None); + let mut output = Output::new(output_pin.reborrow(), Level::High); + let mut input = Input::new(input_pin.reborrow(), Pull::None); assert!(input.is_high(), "input was expected to be high"); @@ -120,8 +123,8 @@ async fn main(_spawner: Spawner) { { info!("test wait_for_any_edge (rising)"); - let mut output = Output::new(&mut output_pin, Level::Low); - let mut input = Input::new(&mut input_pin, Pull::None); + let mut output = Output::new(output_pin.reborrow(), Level::Low); + let mut input = Input::new(input_pin.reborrow(), Pull::None); assert!(input.is_low(), "input was expected to be low"); diff --git a/tests/rp/src/bin/gpio_multicore.rs b/tests/rp/src/bin/gpio_multicore.rs index e9c6f3122..857f36975 100644 --- a/tests/rp/src/bin/gpio_multicore.rs +++ b/tests/rp/src/bin/gpio_multicore.rs @@ -1,12 +1,16 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{info, unwrap}; use embassy_executor::Executor; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_rp::multicore::{spawn_core1, Stack}; use embassy_rp::peripherals::{PIN_0, PIN_1}; +use embassy_rp::Peri; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::channel::Channel; use static_cell::StaticCell; @@ -34,7 +38,7 @@ fn main() -> ! { } #[embassy_executor::task] -async fn core0_task(p: PIN_0) { +async fn core0_task(p: Peri<'static, PIN_0>) { info!("CORE0 is running"); let mut pin = Output::new(p, Level::Low); @@ -51,12 +55,12 @@ async fn core0_task(p: PIN_0) { } #[embassy_executor::task] -async fn core1_task(p: PIN_1) { +async fn core1_task(p: Peri<'static, PIN_1>) { info!("CORE1 is running"); CHANNEL0.receive().await; - let mut pin = Input::new(p, Pull::Down); + let mut pin = Input::new(p, Pull::None); let wait = pin.wait_for_rising_edge(); CHANNEL1.send(()).await; diff --git a/tests/rp/src/bin/i2c.rs b/tests/rp/src/bin/i2c.rs index 9615007bd..2c835bd5a 100644 --- a/tests/rp/src/bin/i2c.rs +++ b/tests/rp/src/bin/i2c.rs @@ -1,23 +1,21 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); -use defmt::{assert_eq, info, panic, unwrap}; +use defmt::{assert_eq, info, panic}; use embassy_embedded_hal::SetConfig; -use embassy_executor::{Executor, Spawner}; +use embassy_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}; use embedded_hal_1::i2c::Operation; use embedded_hal_async::i2c::I2c; -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 EXECUTOR1: StaticCell = StaticCell::new(); - use crate::i2c::AbortReason; bind_interrupts!(struct Irqs { @@ -106,7 +104,7 @@ async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { } async fn controller_task(con: &mut i2c::I2c<'static, I2C0, i2c::Async>) { - info!("Device start"); + info!("Controller start"); { let buf = [0xCA, 0x11]; @@ -180,7 +178,7 @@ async fn controller_task(con: &mut i2c::I2c<'static, I2C0, i2c::Async>) { } #[embassy_executor::main] - async fn main(_core0_spawner: Spawner) { + async fn main(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. @@ -210,14 +208,7 @@ async fn controller_task(con: &mut i2c::I2c<'static, I2C0, i2c::Async>) { 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 *core::ptr::addr_of_mut!(CORE1_STACK) }, - move || { - let executor1 = EXECUTOR1.init(Executor::new()); - executor1.run(|spawner| unwrap!(spawner.spawn(device_task(device)))); - }, - ); + spawner.must_spawn(device_task(device)); let c_sda = p.PIN_21; let c_scl = p.PIN_20; diff --git a/tests/rp/src/bin/multicore.rs b/tests/rp/src/bin/multicore.rs index 783ea0f27..902169c40 100644 --- a/tests/rp/src/bin/multicore.rs +++ b/tests/rp/src/bin/multicore.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{info, unwrap}; use embassy_executor::Executor; diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs new file mode 100644 index 000000000..167a26eb2 --- /dev/null +++ b/tests/rp/src/bin/overclock.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "rp2040")] +teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig, CoreVoltage}; +use embassy_rp::config::Config; +use embassy_time::Instant; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + + // Initialize with 200MHz clock configuration + config.clocks = ClockConfig::system_freq(200_000_000).unwrap(); + + // if we are rp235x, we need to manually set the core voltage. rp2040 should do this automatically + #[cfg(feature = "rp235xb")] + { + config.clocks.core_voltage = CoreVoltage::V1_15; + } + + let _p = embassy_rp::init(config); + + // We should be at core voltage of 1.15V + assert_eq!(core_voltage().unwrap(), CoreVoltage::V1_15, "Core voltage is not 1.15V"); + // We should be at 200MHz + assert_eq!(clk_sys_freq(), 200_000_000, "System clock frequency is not 200MHz"); + + // Test the system speed + let time_elapsed = { + let mut counter = 0; + let start = Instant::now(); + while counter < COUNT_TO { + counter += 1; + } + let elapsed = Instant::now() - start; + + elapsed.as_millis() + }; + + // Tests will fail if unused variables are detected: + // Report the elapsed time, so that the compiler doesn't optimize it away for the chip not on test + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + clk_sys_freq() / 1_000_000, + COUNT_TO, + time_elapsed + ); + + // Check if the elapsed time is within expected limits + // for rp2040 we expect about 600ms + #[cfg(feature = "rp2040")] + // allow 1% error + assert!(time_elapsed < 606, "Elapsed time is too long"); + // for rp235x we expect about 450ms + #[cfg(feature = "rp235xb")] + assert!(time_elapsed < 455, "Elapsed time is too long"); + + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/pio_irq.rs b/tests/rp/src/bin/pio_irq.rs index 33cdaaac9..dc37dd83d 100644 --- a/tests/rp/src/bin/pio_irq.rs +++ b/tests/rp/src/bin/pio_irq.rs @@ -1,11 +1,15 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::info; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Config, InterruptHandler, Pio}; use {defmt_rtt as _, panic_probe as _}; @@ -24,7 +28,7 @@ async fn main(_spawner: Spawner) { .. } = Pio::new(pio, Irqs); - let prg = pio_proc::pio_asm!( + let prg = pio_asm!( "irq set 0", "irq wait 0", "irq set 1", diff --git a/tests/rp/src/bin/pio_multi_load.rs b/tests/rp/src/bin/pio_multi_load.rs index cd28f99b6..aca476d56 100644 --- a/tests/rp/src/bin/pio_multi_load.rs +++ b/tests/rp/src/bin/pio_multi_load.rs @@ -1,11 +1,15 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::info; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::program::pio_asm; use embassy_rp::pio::{Config, InterruptHandler, LoadError, Pio}; use {defmt_rtt as _, panic_probe as _}; @@ -27,7 +31,7 @@ async fn main(_spawner: Spawner) { } = Pio::new(pio, Irqs); // load with explicit origin works - let prg1 = pio_proc::pio_asm!( + let prg1 = pio_asm!( ".origin 4" "nop", "nop", @@ -46,15 +50,14 @@ async fn main(_spawner: Spawner) { assert_eq!(loaded1.wrap.target, 4); // load without origin chooses a free space - let prg2 = pio_proc::pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 1", "nop", "nop",); + let prg2 = pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 1", "nop", "nop",); let loaded2 = common.load_program(&prg2.program); assert_eq!(loaded2.origin, 14); assert_eq!(loaded2.wrap.source, 23); assert_eq!(loaded2.wrap.target, 14); // wrapping around the end of program space automatically works - let prg3 = - pio_proc::pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 2",); + let prg3 = pio_asm!("nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "nop", "irq 2",); let loaded3 = common.load_program(&prg3.program); assert_eq!(loaded3.origin, 24); assert_eq!(loaded3.wrap.source, 3); @@ -88,13 +91,13 @@ async fn main(_spawner: Spawner) { // instruction memory is full now. all loads should fail. { - let prg = pio_proc::pio_asm!(".origin 0", "nop"); + let prg = pio_asm!(".origin 0", "nop"); match common.try_load_program(&prg.program) { Err(LoadError::AddressInUse(0)) => (), _ => panic!("program loaded when it shouldn't"), }; - let prg = pio_proc::pio_asm!("nop"); + let prg = pio_asm!("nop"); match common.try_load_program(&prg.program) { Err(LoadError::InsufficientSpace) => (), _ => panic!("program loaded when it shouldn't"), @@ -106,13 +109,13 @@ async fn main(_spawner: Spawner) { common.free_instr(loaded3.used_memory); } { - let prg = pio_proc::pio_asm!(".origin 0", "nop"); + let prg = pio_asm!(".origin 0", "nop"); match common.try_load_program(&prg.program) { Ok(_) => (), _ => panic!("program didn't loaded when it shouldn"), }; - let prg = pio_proc::pio_asm!("nop"); + let prg = pio_asm!("nop"); match common.try_load_program(&prg.program) { Ok(_) => (), _ => panic!("program didn't loaded when it shouldn"), diff --git a/tests/rp/src/bin/pwm.rs b/tests/rp/src/bin/pwm.rs index c05197000..5f890cd50 100644 --- a/tests/rp/src/bin/pwm.rs +++ b/tests/rp/src/bin/pwm.rs @@ -1,10 +1,15 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert, assert_eq, assert_ne, *}; use embassy_executor::Spawner; -use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::gpio::{Input, Pull}; +#[cfg(feature = "rp2040")] +use embassy_rp::gpio::{Level, Output}; use embassy_rp::pwm::{Config, InputMode, Pwm}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -28,7 +33,7 @@ async fn main(_spawner: Spawner) { // Test free-running clock { - let pwm = Pwm::new_free(&mut p.PWM_SLICE3, cfg.clone()); + let pwm = Pwm::new_free(p.PWM_SLICE3.reborrow(), cfg.clone()); cortex_m::asm::delay(125); let ctr = pwm.counter(); assert!(ctr > 0); @@ -45,8 +50,8 @@ async fn main(_spawner: Spawner) { // Test output from A { - let pin1 = Input::new(&mut p9, Pull::None); - let _pwm = Pwm::new_output_a(&mut p.PWM_SLICE3, &mut p6, cfg.clone()); + let pin1 = Input::new(p9.reborrow(), Pull::None); + let _pwm = Pwm::new_output_a(p.PWM_SLICE3.reborrow(), p6.reborrow(), cfg.clone()); Timer::after_millis(1).await; assert_eq!(pin1.is_low(), invert_a); Timer::after_millis(5).await; @@ -59,8 +64,8 @@ async fn main(_spawner: Spawner) { // Test output from B { - let pin2 = Input::new(&mut p11, Pull::None); - let _pwm = Pwm::new_output_b(&mut p.PWM_SLICE3, &mut p7, cfg.clone()); + let pin2 = Input::new(p11.reborrow(), Pull::None); + let _pwm = Pwm::new_output_b(p.PWM_SLICE3.reborrow(), p7.reborrow(), cfg.clone()); Timer::after_millis(1).await; assert_ne!(pin2.is_low(), invert_a); Timer::after_millis(5).await; @@ -73,9 +78,9 @@ async fn main(_spawner: Spawner) { // Test output from A+B { - let pin1 = Input::new(&mut p9, Pull::None); - let pin2 = Input::new(&mut p11, Pull::None); - let _pwm = Pwm::new_output_ab(&mut p.PWM_SLICE3, &mut p6, &mut p7, cfg.clone()); + let pin1 = Input::new(p9.reborrow(), Pull::None); + let pin2 = Input::new(p11.reborrow(), Pull::None); + let _pwm = Pwm::new_output_ab(p.PWM_SLICE3.reborrow(), p6.reborrow(), p7.reborrow(), cfg.clone()); Timer::after_millis(1).await; assert_eq!(pin1.is_low(), invert_a); assert_ne!(pin2.is_low(), invert_a); @@ -92,9 +97,16 @@ async fn main(_spawner: Spawner) { } // Test level-gated + #[cfg(feature = "rp2040")] { - let mut pin2 = Output::new(&mut p11, Level::Low); - let pwm = Pwm::new_input(&mut p.PWM_SLICE3, &mut p7, Pull::None, InputMode::Level, cfg.clone()); + let mut pin2 = Output::new(p11.reborrow(), Level::Low); + let pwm = Pwm::new_input( + p.PWM_SLICE3.reborrow(), + p7.reborrow(), + Pull::None, + InputMode::Level, + cfg.clone(), + ); assert_eq!(pwm.counter(), 0); Timer::after_millis(5).await; assert_eq!(pwm.counter(), 0); @@ -102,17 +114,19 @@ async fn main(_spawner: Spawner) { Timer::after_millis(1).await; pin2.set_low(); let ctr = pwm.counter(); + info!("ctr: {}", ctr); assert!(ctr >= 1000); Timer::after_millis(1).await; assert_eq!(pwm.counter(), ctr); } // Test rising-gated + #[cfg(feature = "rp2040")] { - let mut pin2 = Output::new(&mut p11, Level::Low); + let mut pin2 = Output::new(p11.reborrow(), Level::Low); let pwm = Pwm::new_input( - &mut p.PWM_SLICE3, - &mut p7, + p.PWM_SLICE3.reborrow(), + p7.reborrow(), Pull::None, InputMode::RisingEdge, cfg.clone(), @@ -129,11 +143,12 @@ async fn main(_spawner: Spawner) { } // Test falling-gated + #[cfg(feature = "rp2040")] { - let mut pin2 = Output::new(&mut p11, Level::High); + let mut pin2 = Output::new(p11.reborrow(), Level::High); let pwm = Pwm::new_input( - &mut p.PWM_SLICE3, - &mut p7, + p.PWM_SLICE3.reborrow(), + p7.reborrow(), Pull::None, InputMode::FallingEdge, cfg.clone(), @@ -151,10 +166,10 @@ async fn main(_spawner: Spawner) { // pull-down { - let pin2 = Input::new(&mut p11, Pull::None); + let pin2 = Input::new(p11.reborrow(), Pull::None); Pwm::new_input( - &mut p.PWM_SLICE3, - &mut p7, + p.PWM_SLICE3.reborrow(), + p7.reborrow(), Pull::Down, InputMode::FallingEdge, cfg.clone(), @@ -165,10 +180,10 @@ async fn main(_spawner: Spawner) { // pull-up { - let pin2 = Input::new(&mut p11, Pull::None); + let pin2 = Input::new(p11.reborrow(), Pull::None); Pwm::new_input( - &mut p.PWM_SLICE3, - &mut p7, + p.PWM_SLICE3.reborrow(), + p7.reborrow(), Pull::Up, InputMode::FallingEdge, cfg.clone(), diff --git a/tests/rp/src/bin/spi.rs b/tests/rp/src/bin/spi.rs index 4b02942a7..6802bfc99 100644 --- a/tests/rp/src/bin/spi.rs +++ b/tests/rp/src/bin/spi.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/spi_async.rs b/tests/rp/src/bin/spi_async.rs index efdc80b53..e50667435 100644 --- a/tests/rp/src/bin/spi_async.rs +++ b/tests/rp/src/bin/spi_async.rs @@ -3,7 +3,10 @@ //! #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/spinlock_mutex_multicore.rs b/tests/rp/src/bin/spinlock_mutex_multicore.rs new file mode 100644 index 000000000..ebcf1ca32 --- /dev/null +++ b/tests/rp/src/bin/spinlock_mutex_multicore.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] +#[cfg(feature = "rp2040")] +teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); + +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::spinlock_mutex::SpinlockRawMutex; +use embassy_sync::channel::Channel; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<1024> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL0: Channel, bool, 1> = Channel::new(); +static CHANNEL1: Channel, bool, 1> = Channel::new(); + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task()))); + }, + ); + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("CORE0 is running"); + let ping = true; + CHANNEL0.send(ping).await; + let pong = CHANNEL1.receive().await; + assert_eq!(ping, pong); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +#[embassy_executor::task] +async fn core1_task() { + info!("CORE1 is running"); + let ping = CHANNEL0.receive().await; + CHANNEL1.send(ping).await; +} diff --git a/tests/rp/src/bin/timer.rs b/tests/rp/src/bin/timer.rs index be9242144..12a4d7daa 100644 --- a/tests/rp/src/bin/timer.rs +++ b/tests/rp/src/bin/timer.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert, *}; use embassy_executor::Spawner; diff --git a/tests/rp/src/bin/uart.rs b/tests/rp/src/bin/uart.rs index 6e6e5517b..80230f3fe 100644 --- a/tests/rp/src/bin/uart.rs +++ b/tests/rp/src/bin/uart.rs @@ -1,21 +1,24 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; use embassy_rp::gpio::{Level, Output}; -use embassy_rp::uart::{Blocking, Config, Error, Instance, Parity, Uart, UartRx}; +use embassy_rp::uart::{Blocking, Config, Error, Parity, Uart, UartRx}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -fn read(uart: &mut Uart<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { +fn read(uart: &mut Uart<'_, Blocking>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.blocking_read(&mut buf)?; Ok(buf) } -fn read1(uart: &mut UartRx<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { +fn read1(uart: &mut UartRx<'_, Blocking>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.blocking_read(&mut buf)?; Ok(buf) @@ -53,7 +56,7 @@ async fn main(_spawner: Spawner) { { let config = Config::default(); - let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + let mut uart = Uart::new_blocking(uart.reborrow(), tx.reborrow(), rx.reborrow(), config); // We can't send too many bytes, they have to fit in the FIFO. // This is because we aren't sending+receiving at the same time. @@ -66,7 +69,7 @@ async fn main(_spawner: Spawner) { info!("test overflow detection"); { let config = Config::default(); - let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + let mut uart = Uart::new_blocking(uart.reborrow(), tx.reborrow(), rx.reborrow(), config); let data = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, @@ -90,7 +93,7 @@ async fn main(_spawner: Spawner) { info!("test break detection"); { let config = Config::default(); - let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + let mut uart = Uart::new_blocking(uart.reborrow(), tx.reborrow(), rx.reborrow(), config); // break on empty fifo uart.send_break(20).await; @@ -110,11 +113,11 @@ async fn main(_spawner: Spawner) { // parity detection. here we bitbang to not require two uarts. info!("test parity error detection"); { - let mut pin = Output::new(&mut tx, Level::High); + let mut pin = Output::new(tx.reborrow(), Level::High); let mut config = Config::default(); config.baudrate = 1000; config.parity = Parity::ParityEven; - let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config); + let mut uart = UartRx::new_blocking(uart.reborrow(), rx.reborrow(), config); async fn chr(pin: &mut Output<'_>, v: u8, parity: u8) { send(pin, v, Some(parity != 0)).await; @@ -137,10 +140,10 @@ async fn main(_spawner: Spawner) { // framing error detection. here we bitbang because there's no other way. info!("test framing error detection"); { - let mut pin = Output::new(&mut tx, Level::High); + let mut pin = Output::new(tx.reborrow(), Level::High); let mut config = Config::default(); config.baudrate = 1000; - let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config); + let mut uart = UartRx::new_blocking(uart.reborrow(), rx.reborrow(), config); async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { if good { diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs index d68c23cbd..cb78fc142 100644 --- a/tests/rp/src/bin/uart_buffered.rs +++ b/tests/rp/src/bin/uart_buffered.rs @@ -1,13 +1,16 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, panic, *}; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::UART0; -use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Instance, Parity}; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Parity}; use embassy_time::Timer; use embedded_io_async::{Read, ReadExactError, Write}; use {defmt_rtt as _, panic_probe as _}; @@ -16,7 +19,7 @@ bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; }); -async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Result<[u8; N], Error> { +async fn read(uart: &mut BufferedUart) -> Result<[u8; N], Error> { let mut buf = [255; N]; match uart.read_exact(&mut buf).await { Ok(()) => Ok(buf), @@ -26,7 +29,7 @@ async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Res } } -async fn read1(uart: &mut BufferedUartRx<'_, impl Instance>) -> Result<[u8; N], Error> { +async fn read1(uart: &mut BufferedUartRx) -> Result<[u8; N], Error> { let mut buf = [255; N]; match uart.read_exact(&mut buf).await { Ok(()) => Ok(buf), @@ -70,7 +73,15 @@ async fn main(_spawner: Spawner) { let config = Config::default(); let tx_buf = &mut [0u8; 16]; let rx_buf = &mut [0u8; 16]; - let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + let mut uart = BufferedUart::new( + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), + Irqs, + tx_buf, + rx_buf, + config, + ); // Make sure we send more bytes than fits in the FIFO, to test the actual // bufferedUart. @@ -90,7 +101,15 @@ async fn main(_spawner: Spawner) { let config = Config::default(); let tx_buf = &mut [0u8; 16]; let rx_buf = &mut [0u8; 16]; - let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + let mut uart = BufferedUart::new( + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), + Irqs, + tx_buf, + rx_buf, + config, + ); // Make sure we send more bytes than fits in the FIFO, to test the actual // bufferedUart. @@ -125,7 +144,15 @@ async fn main(_spawner: Spawner) { config.baudrate = 1000; let tx_buf = &mut [0u8; 16]; let rx_buf = &mut [0u8; 16]; - let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + let mut uart = BufferedUart::new( + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), + Irqs, + tx_buf, + rx_buf, + config, + ); // break on empty buffer uart.send_break(20).await; @@ -153,13 +180,13 @@ async fn main(_spawner: Spawner) { // parity detection. here we bitbang to not require two uarts. info!("test parity error detection"); { - let mut pin = Output::new(&mut tx, Level::High); + let mut pin = Output::new(tx.reborrow(), Level::High); // choose a very slow baud rate to make tests reliable even with O0 let mut config = Config::default(); config.baudrate = 1000; config.parity = Parity::ParityEven; let rx_buf = &mut [0u8; 16]; - let mut uart = BufferedUartRx::new(&mut uart, Irqs, &mut rx, rx_buf, config); + let mut uart = BufferedUartRx::new(uart.reborrow(), Irqs, rx.reborrow(), rx_buf, config); async fn chr(pin: &mut Output<'_>, v: u8, parity: u32) { send(pin, v, Some(parity != 0)).await; @@ -201,12 +228,12 @@ async fn main(_spawner: Spawner) { // framing error detection. here we bitbang because there's no other way. info!("test framing error detection"); { - let mut pin = Output::new(&mut tx, Level::High); + let mut pin = Output::new(tx.reborrow(), Level::High); // choose a very slow baud rate to make tests reliable even with O0 let mut config = Config::default(); config.baudrate = 1000; let rx_buf = &mut [0u8; 16]; - let mut uart = BufferedUartRx::new(&mut uart, Irqs, &mut rx, rx_buf, config); + let mut uart = BufferedUartRx::new(uart.reborrow(), Irqs, rx.reborrow(), rx_buf, config); async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { if good { diff --git a/tests/rp/src/bin/uart_dma.rs b/tests/rp/src/bin/uart_dma.rs index edc87175a..a7af81f5f 100644 --- a/tests/rp/src/bin/uart_dma.rs +++ b/tests/rp/src/bin/uart_dma.rs @@ -1,13 +1,16 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::UART0; -use embassy_rp::uart::{Async, Config, Error, Instance, InterruptHandler, Parity, Uart, UartRx}; +use embassy_rp::uart::{Async, Config, Error, InterruptHandler, Parity, Uart, UartRx}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -15,13 +18,13 @@ bind_interrupts!(struct Irqs { UART0_IRQ => InterruptHandler; }); -async fn read(uart: &mut Uart<'_, impl Instance, Async>) -> Result<[u8; N], Error> { +async fn read(uart: &mut Uart<'_, Async>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.read(&mut buf).await?; Ok(buf) } -async fn read1(uart: &mut UartRx<'_, impl Instance, Async>) -> Result<[u8; N], Error> { +async fn read1(uart: &mut UartRx<'_, Async>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.read(&mut buf).await?; Ok(buf) @@ -62,12 +65,12 @@ async fn main(_spawner: Spawner) { { let config = Config::default(); let mut uart = Uart::new( - &mut uart, - &mut tx, - &mut rx, + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), Irqs, - &mut p.DMA_CH0, - &mut p.DMA_CH1, + p.DMA_CH0.reborrow(), + p.DMA_CH1.reborrow(), config, ); @@ -83,12 +86,12 @@ async fn main(_spawner: Spawner) { { let config = Config::default(); let mut uart = Uart::new( - &mut uart, - &mut tx, - &mut rx, + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), Irqs, - &mut p.DMA_CH0, - &mut p.DMA_CH1, + p.DMA_CH0.reborrow(), + p.DMA_CH1.reborrow(), config, ); @@ -112,12 +115,12 @@ async fn main(_spawner: Spawner) { { let config = Config::default(); let (mut tx, mut rx) = Uart::new( - &mut uart, - &mut tx, - &mut rx, + uart.reborrow(), + tx.reborrow(), + rx.reborrow(), Irqs, - &mut p.DMA_CH0, - &mut p.DMA_CH1, + p.DMA_CH0.reborrow(), + p.DMA_CH1.reborrow(), config, ) .split(); @@ -153,12 +156,12 @@ async fn main(_spawner: Spawner) { // parity detection. here we bitbang to not require two uarts. info!("test parity error detection"); { - let mut pin = Output::new(&mut tx, Level::High); + let mut pin = Output::new(tx.reborrow(), Level::High); // choose a very slow baud rate to make tests reliable even with O0 let mut config = Config::default(); config.baudrate = 1000; config.parity = Parity::ParityEven; - let mut uart = UartRx::new(&mut uart, &mut rx, Irqs, &mut p.DMA_CH0, config); + let mut uart = UartRx::new(uart.reborrow(), rx.reborrow(), Irqs, p.DMA_CH0.reborrow(), config); async fn chr(pin: &mut Output<'_>, v: u8, parity: u32) { send(pin, v, Some(parity != 0)).await; @@ -199,11 +202,11 @@ async fn main(_spawner: Spawner) { // framing error detection. here we bitbang because there's no other way. info!("test framing error detection"); { - let mut pin = Output::new(&mut tx, Level::High); + let mut pin = Output::new(tx.reborrow(), Level::High); // choose a very slow baud rate to make tests reliable even with O0 let mut config = Config::default(); config.baudrate = 1000; - let mut uart = UartRx::new(&mut uart, &mut rx, Irqs, &mut p.DMA_CH0, config); + let mut uart = UartRx::new(uart.reborrow(), rx.reborrow(), Irqs, p.DMA_CH0.reborrow(), config); async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { if good { diff --git a/tests/rp/src/bin/uart_upgrade.rs b/tests/rp/src/bin/uart_upgrade.rs index 603e20f2f..f658b6b8c 100644 --- a/tests/rp/src/bin/uart_upgrade.rs +++ b/tests/rp/src/bin/uart_upgrade.rs @@ -1,6 +1,9 @@ #![no_std] #![no_main] +#[cfg(feature = "rp2040")] teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 8752da59b..d94342598 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -9,7 +9,6 @@ runner = "teleprobe client run" rustflags = [ # Code-size optimizations. #"-Z", "trap-unreachable=no", - "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index 2eac636e5..8d10f6593 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -12,19 +12,19 @@ stm32f207zg = ["embassy-stm32/stm32f207zg", "spi-v1", "chrono", "not-gpdma", "et stm32f303ze = ["embassy-stm32/stm32f303ze", "chrono", "not-gpdma"] stm32f429zi = ["embassy-stm32/stm32f429zi", "spi-v1", "chrono", "eth", "stop", "can", "not-gpdma", "dac", "rng"] stm32f446re = ["embassy-stm32/stm32f446re", "spi-v1", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] -stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] +stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng", "single-bank"] stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"] stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan", "cordic"] stm32h563zi = ["embassy-stm32/stm32h563zi", "spi-v345", "chrono", "eth", "rng", "fdcan", "hash", "cordic", "stop"] stm32h753zi = ["embassy-stm32/stm32h753zi", "spi-v345", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"] stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "spi-v345", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"] stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "spi-v345", "not-gpdma", "rng", "fdcan"] -stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] -stm32l152re = ["embassy-stm32/stm32l152re", "spi-v1", "chrono", "not-gpdma"] +stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng", "eeprom"] +stm32l152re = ["embassy-stm32/stm32l152re", "spi-v1", "chrono", "not-gpdma", "eeprom"] stm32l496zg = ["embassy-stm32/stm32l496zg", "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", "hash"] +stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng", "dual-bank"] +stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash", "dual-bank"] stm32u585ai = ["embassy-stm32/stm32u585ai", "spi-v345", "chrono", "rng", "hash", "cordic"] stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "spi-v345", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] @@ -39,7 +39,7 @@ spi-v1 = [] spi-v345 = [] cryp = [] hash = [] -eth = ["embassy-executor/task-arena-size-16384"] +eth = [] rng = [] sdmmc = [] stop = ["embassy-stm32/low-power", "embassy-stm32/low-power-debug-with-sleep"] @@ -53,23 +53,26 @@ not-gpdma = [] dac = [] ucpd = [] cordic = ["dep:num-traits"] +dual-bank = ["embassy-stm32/dual-bank"] +single-bank = ["embassy-stm32/single-bank"] +eeprom = [] cm0 = ["portable-atomic/unsafe-assume-single-core"] [dependencies] teleprobe-meta = "1" -embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "memory-x", "time-driver-any"] } +embassy-sync = { version = "0.7.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "memory-x", "time-driver-any"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] } -embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-net = { version = "0.7.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } perf-client = { path = "../perf-client" } -defmt = "0.3.0" -defmt-rtt = "0.4" +defmt = "1.0.1" +defmt-rtt = "1.0.0" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" @@ -78,17 +81,17 @@ 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 } -rand_chacha = { version = "0.3", default-features = false } +panic-probe = { version = "1.0.0", features = ["print-defmt"] } +rand_core = { version = "0.9.1", default-features = false } +rand_chacha = { version = "0.9.0", default-features = false } 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"] } -num-traits = {version="0.2", default-features = false,features = ["libm"], optional = true} +aes-gcm = { version = "0.10.3", default-features = false, features = ["aes", "heapless"] } +num-traits = { version="0.2", default-features = false,features = ["libm"], optional = true} # BEGIN TESTS # Generated by gen_test.py. DO NOT EDIT. @@ -117,6 +120,11 @@ name = "dac_l1" path = "src/bin/dac_l1.rs" required-features = [ "stm32l152re",] +[[bin]] +name = "eeprom" +path = "src/bin/eeprom.rs" +required-features = [ "eeprom",] + [[bin]] name = "eth" path = "src/bin/eth.rs" diff --git a/tests/stm32/src/bin/can.rs b/tests/stm32/src/bin/can.rs index 85a5f8d83..778d88a7b 100644 --- a/tests/stm32/src/bin/can.rs +++ b/tests/stm32/src/bin/can.rs @@ -43,7 +43,7 @@ async fn main(_spawner: Spawner) { // To synchronise to the bus the RX input needs to see a high level. // Use `mem::forget()` to release the borrow on the pin but keep the // pull-up resistor enabled. - let rx_pin = Input::new(&mut rx, Pull::Up); + let rx_pin = Input::new(rx.reborrow(), Pull::Up); core::mem::forget(rx_pin); let mut can = embassy_stm32::can::Can::new(can, rx, tx, Irqs); diff --git a/tests/stm32/src/bin/cordic.rs b/tests/stm32/src/bin/cordic.rs index 879ad56b6..e86eea58b 100644 --- a/tests/stm32/src/bin/cordic.rs +++ b/tests/stm32/src/bin/cordic.rs @@ -82,8 +82,8 @@ async fn main(_spawner: Spawner) { let cnt1 = defmt::unwrap!( cordic .async_calc_32bit( - &mut write_dma, - &mut read_dma, + write_dma.reborrow(), + read_dma.reborrow(), &input_q1_31[2..], &mut output_q1_31[cnt0..], true, diff --git a/tests/stm32/src/bin/dac.rs b/tests/stm32/src/bin/dac.rs index 88e661525..d34bbb255 100644 --- a/tests/stm32/src/bin/dac.rs +++ b/tests/stm32/src/bin/dac.rs @@ -12,7 +12,6 @@ use defmt::assert; use embassy_executor::Spawner; use embassy_stm32::adc::Adc; use embassy_stm32::dac::{DacCh1, Value}; -use embassy_stm32::dma::NoDma; use embassy_time::Timer; use micromath::F32Ext; use {defmt_rtt as _, panic_probe as _}; @@ -27,7 +26,7 @@ async fn main(_spawner: Spawner) { let dac_pin = peri!(p, DAC_PIN); let mut adc_pin = unsafe { core::ptr::read(&dac_pin) }; - let mut dac = DacCh1::new(dac, NoDma, dac_pin); + let mut dac = DacCh1::new_blocking(dac, dac_pin); let mut adc = Adc::new(adc); #[cfg(feature = "stm32h755zi")] diff --git a/tests/stm32/src/bin/dac_l1.rs b/tests/stm32/src/bin/dac_l1.rs index 925db617d..e6400f28e 100644 --- a/tests/stm32/src/bin/dac_l1.rs +++ b/tests/stm32/src/bin/dac_l1.rs @@ -12,7 +12,6 @@ use defmt::assert; use embassy_executor::Spawner; use embassy_stm32::adc::Adc; use embassy_stm32::dac::{DacCh1, Value}; -use embassy_stm32::dma::NoDma; use embassy_stm32::{bind_interrupts, peripherals}; use embassy_time::Timer; use micromath::F32Ext; @@ -32,7 +31,7 @@ async fn main(_spawner: Spawner) { let dac_pin = peri!(p, DAC_PIN); let mut adc_pin = unsafe { core::ptr::read(&dac_pin) }; - let mut dac = DacCh1::new(dac, NoDma, dac_pin); + let mut dac = DacCh1::new_blocking(dac, dac_pin); let mut adc = Adc::new(adc, Irqs); #[cfg(feature = "stm32h755zi")] diff --git a/tests/stm32/src/bin/eeprom.rs b/tests/stm32/src/bin/eeprom.rs new file mode 100644 index 000000000..61d249fd7 --- /dev/null +++ b/tests/stm32/src/bin/eeprom.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] + +// required-features: eeprom + +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::assert_eq; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize the board and obtain a Peripherals instance + let p: embassy_stm32::Peripherals = init(); + + let mut f = Flash::new_blocking(p.FLASH); + const ADDR: u32 = 0x0; + + unwrap!(f.eeprom_write_slice(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + let mut buf = [0u8; 8]; + unwrap!(f.eeprom_read_slice(ADDR, &mut buf)); + assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/src/bin/eth.rs b/tests/stm32/src/bin/eth.rs index 9da514881..bcb362b42 100644 --- a/tests/stm32/src/bin/eth.rs +++ b/tests/stm32/src/bin/eth.rs @@ -6,13 +6,11 @@ mod common; use common::*; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; -use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_net::StackResources; +use embassy_stm32::eth::{Ethernet, GenericPhy, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::{bind_interrupts, eth, peripherals, rng}; -use rand_core::RngCore; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -29,11 +27,11 @@ bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; }); -type Device = Ethernet<'static, ETH, GenericSMI>; +type Device = Ethernet<'static, ETH, GenericPhy>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -87,7 +85,7 @@ async fn main(spawner: Spawner) { #[cfg(feature = "stm32h563zi")] p.PB15, p.PG11, - GenericSMI::new(0), + GenericPhy::new_auto(), mac_addr, ); @@ -99,12 +97,11 @@ async fn main(spawner: Spawner) { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/stm32/src/bin/gpio.rs b/tests/stm32/src/bin/gpio.rs index 4a2584b4e..40b03201c 100644 --- a/tests/stm32/src/bin/gpio.rs +++ b/tests/stm32/src/bin/gpio.rs @@ -20,10 +20,10 @@ async fn main(_spawner: Spawner) { // Test initial output { - let b = Input::new(&mut b, Pull::None); + let b = Input::new(b.reborrow(), Pull::None); { - let a = Output::new(&mut a, Level::Low, Speed::Low); + let a = Output::new(a.reborrow(), Level::Low, Speed::Low); delay(); assert!(b.is_low()); assert!(!b.is_high()); @@ -31,7 +31,7 @@ async fn main(_spawner: Spawner) { assert!(!a.is_set_high()); } { - let mut a = Output::new(&mut a, Level::High, Speed::Low); + let mut a = Output::new(a.reborrow(), Level::High, Speed::Low); delay(); assert!(!b.is_low()); assert!(b.is_high()); @@ -68,10 +68,10 @@ async fn main(_spawner: Spawner) { // Test input no pull { - let b = Input::new(&mut b, Pull::None); + let b = Input::new(b.reborrow(), Pull::None); // no pull, the status is undefined - let mut a = Output::new(&mut a, Level::Low, Speed::Low); + let mut a = Output::new(a.reborrow(), Level::Low, Speed::Low); delay(); assert!(b.is_low()); a.set_high(); @@ -81,11 +81,11 @@ async fn main(_spawner: Spawner) { // Test input pulldown { - let b = Input::new(&mut b, Pull::Down); + let b = Input::new(b.reborrow(), Pull::Down); delay(); assert!(b.is_low()); - let mut a = Output::new(&mut a, Level::Low, Speed::Low); + let mut a = Output::new(a.reborrow(), Level::Low, Speed::Low); delay(); assert!(b.is_low()); a.set_high(); @@ -95,11 +95,11 @@ async fn main(_spawner: Spawner) { // Test input pullup { - let b = Input::new(&mut b, Pull::Up); + let b = Input::new(b.reborrow(), Pull::Up); delay(); assert!(b.is_high()); - let mut a = Output::new(&mut a, Level::Low, Speed::Low); + let mut a = Output::new(a.reborrow(), Level::Low, Speed::Low); delay(); assert!(b.is_low()); a.set_high(); @@ -109,10 +109,10 @@ async fn main(_spawner: Spawner) { // Test output open drain { - let b = Input::new(&mut b, Pull::Down); + let b = Input::new(b.reborrow(), Pull::Down); // no pull, the status is undefined - let mut a = OutputOpenDrain::new(&mut a, Level::Low, Speed::Low); + let mut a = OutputOpenDrain::new(a.reborrow(), Level::Low, Speed::Low); delay(); assert!(b.is_low()); a.set_high(); // High-Z output @@ -124,12 +124,12 @@ async fn main(_spawner: Spawner) { // Test initial output { //Flex pin configured as input - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(Pull::None); { //Flex pin configured as output - let mut a = Flex::new(&mut a); //Flex pin configured as output + let mut a = Flex::new(a.reborrow()); //Flex pin configured as output a.set_low(); // Pin state must be set before configuring the pin, thus we avoid unknown state a.set_as_output(Speed::Low); delay(); @@ -137,7 +137,7 @@ async fn main(_spawner: Spawner) { } { //Flex pin configured as output - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_high(); a.set_as_output(Speed::Low); @@ -148,10 +148,10 @@ async fn main(_spawner: Spawner) { // Test input no pull { - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(Pull::None); // no pull, the status is undefined - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_low(); a.set_as_output(Speed::Low); @@ -164,12 +164,12 @@ async fn main(_spawner: Spawner) { // Test input pulldown { - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(Pull::Down); delay(); assert!(b.is_low()); - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_low(); a.set_as_output(Speed::Low); delay(); @@ -181,12 +181,12 @@ async fn main(_spawner: Spawner) { // Test input pullup { - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(Pull::Up); delay(); assert!(b.is_high()); - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_high(); a.set_as_output(Speed::Low); delay(); @@ -198,10 +198,10 @@ async fn main(_spawner: Spawner) { // Test output open drain { - let mut b = Flex::new(&mut b); + let mut b = Flex::new(b.reborrow()); b.set_as_input(Pull::Down); - let mut a = Flex::new(&mut a); + let mut a = Flex::new(a.reborrow()); a.set_low(); a.set_as_input_output(Speed::Low); delay(); diff --git a/tests/stm32/src/bin/hash.rs b/tests/stm32/src/bin/hash.rs index bdb3c9a69..52b84a499 100644 --- a/tests/stm32/src/bin/hash.rs +++ b/tests/stm32/src/bin/hash.rs @@ -6,7 +6,6 @@ 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}; @@ -36,7 +35,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let p: embassy_stm32::Peripherals = init(); - let mut hw_hasher = Hash::new(p.HASH, NoDma, Irqs); + let mut hw_hasher = Hash::new_blocking(p.HASH, Irqs); let test_1: &[u8] = b"as;dfhaslfhas;oifvnasd;nifvnhasd;nifvhndlkfghsd;nvfnahssdfgsdafgsasdfasdfasdfasdfasdfghjklmnbvcalskdjghalskdjgfbaslkdjfgbalskdjgbalskdjbdfhsdfhsfghsfghfgh"; let test_2: &[u8] = b"fdhalksdjfhlasdjkfhalskdjfhgal;skdjfgalskdhfjgalskdjfglafgadfgdfgdafgaadsfgfgdfgadrgsyfthxfgjfhklhjkfgukhulkvhlvhukgfhfsrghzdhxyfufynufyuszeradrtydyytserr"; diff --git a/tests/stm32/src/bin/sdmmc.rs b/tests/stm32/src/bin/sdmmc.rs index a6bc117c0..34a53a725 100644 --- a/tests/stm32/src/bin/sdmmc.rs +++ b/tests/stm32/src/bin/sdmmc.rs @@ -34,27 +34,29 @@ async fn main(_spawner: Spawner) { pattern1[i] = i as u8; pattern2[i] = !i as u8; } + let patterns = [pattern1.clone(), pattern2.clone()]; let mut block = DataBlock([0u8; 512]); + let mut blocks = [DataBlock([0u8; 512]), DataBlock([0u8; 512])]; // ======== Try 4bit. ============== info!("initializing in 4-bit mode..."); let mut s = Sdmmc::new_4bit( - &mut sdmmc, + sdmmc.reborrow(), Irqs, - &mut dma, - &mut clk, - &mut cmd, - &mut d0, - &mut d1, - &mut d2, - &mut d3, + dma.reborrow(), + clk.reborrow(), + cmd.reborrow(), + d0.reborrow(), + d1.reborrow(), + d2.reborrow(), + d3.reborrow(), Default::default(), ); let mut err = None; loop { - match s.init_card(mhz(24)).await { + match s.init_sd_card(mhz(24)).await { Ok(_) => break, Err(e) => { if err != Some(e) { @@ -84,23 +86,30 @@ async fn main(_spawner: Spawner) { s.read_block(block_idx, &mut block).await.unwrap(); assert_eq!(block, pattern2); + info!("writing blocks [pattern1, pattern2]..."); + s.write_blocks(block_idx, &patterns).await.unwrap(); + + info!("reading blocks..."); + s.read_blocks(block_idx, &mut blocks).await.unwrap(); + assert_eq!(&blocks, &patterns); + drop(s); // ======== Try 1bit. ============== info!("initializing in 1-bit mode..."); let mut s = Sdmmc::new_1bit( - &mut sdmmc, + sdmmc.reborrow(), Irqs, - &mut dma, - &mut clk, - &mut cmd, - &mut d0, + dma.reborrow(), + clk.reborrow(), + cmd.reborrow(), + d0.reborrow(), Default::default(), ); let mut err = None; loop { - match s.init_card(mhz(24)).await { + match s.init_sd_card(mhz(24)).await { Ok(_) => break, Err(e) => { if err != Some(e) { @@ -116,9 +125,9 @@ async fn main(_spawner: Spawner) { info!("Card: {:#?}", Debug2Format(card)); info!("Clock: {}", s.clock()); - info!("reading pattern2 written in 4bit mode..."); + info!("reading pattern1 written in 4bit mode..."); s.read_block(block_idx, &mut block).await.unwrap(); - assert_eq!(block, pattern2); + assert_eq!(block, pattern1); info!("writing pattern1..."); s.write_block(block_idx, &pattern1).await.unwrap(); @@ -134,6 +143,13 @@ async fn main(_spawner: Spawner) { s.read_block(block_idx, &mut block).await.unwrap(); assert_eq!(block, pattern2); + info!("writing blocks [pattern1, pattern2]..."); + s.write_blocks(block_idx, &patterns).await.unwrap(); + + info!("reading blocks..."); + s.read_blocks(block_idx, &mut blocks).await.unwrap(); + assert_eq!(&blocks, &patterns); + drop(s); info!("Test OK"); diff --git a/tests/stm32/src/bin/spi.rs b/tests/stm32/src/bin/spi.rs index 53d44a94a..e8310866a 100644 --- a/tests/stm32/src/bin/spi.rs +++ b/tests/stm32/src/bin/spi.rs @@ -7,7 +7,8 @@ use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::spi::{self, Spi}; +use embassy_stm32::mode::Blocking; +use embassy_stm32::spi::{self, Spi, Word}; use embassy_stm32::time::Hertz; #[embassy_executor::main] @@ -24,18 +25,65 @@ async fn main(_spawner: Spawner) { spi_config.frequency = Hertz(1_000_000); let mut spi = Spi::new_blocking( - &mut spi_peri, - &mut sck, // Arduino D13 - &mut mosi, // Arduino D11 - &mut miso, // Arduino D12 + spi_peri.reborrow(), + sck.reborrow(), // Arduino D13 + mosi.reborrow(), // Arduino D11 + miso.reborrow(), // Arduino D12 spi_config, ); - let data: [u8; 9] = [0x00, 0xFF, 0xAA, 0x55, 0xC0, 0xFF, 0xEE, 0xC0, 0xDE]; + test_txrx::(&mut spi); + test_txrx::(&mut spi); + + // Assert the RCC bit gets disabled on drop. + #[cfg(feature = "stm32f429zi")] + defmt::assert!(embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + drop(spi); + #[cfg(feature = "stm32f429zi")] + defmt::assert!(!embassy_stm32::pac::RCC.apb2enr().read().spi1en()); + + // test rx-only configuration + let mut spi = Spi::new_blocking_rxonly(spi_peri.reborrow(), sck.reborrow(), miso.reborrow(), spi_config); + let mut mosi_out = Output::new(mosi.reborrow(), Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out); + test_rx::(&mut spi, &mut mosi_out); + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_blocking_txonly(spi_peri.reborrow(), sck.reborrow(), mosi.reborrow(), spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + let mut spi = Spi::new_blocking_txonly_nosck(spi_peri.reborrow(), mosi.reborrow(), spi_config); + test_tx::(&mut spi); + test_tx::(&mut spi); + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. // so we should get the data we sent back. - let mut buf = [0; 9]; + let mut buf = [W::default(); 9]; spi.blocking_transfer(&mut buf, &data).unwrap(); assert_eq!(buf, data); @@ -59,47 +107,33 @@ async fn main(_spawner: Spawner) { spi.blocking_transfer_in_place::(&mut []).unwrap(); spi.blocking_read::(&mut []).unwrap(); spi.blocking_write::(&[]).unwrap(); +} - // Assert the RCC bit gets disabled on drop. - #[cfg(feature = "stm32f429zi")] - defmt::assert!(embassy_stm32::pac::RCC.apb2enr().read().spi1en()); - drop(spi); - #[cfg(feature = "stm32f429zi")] - defmt::assert!(!embassy_stm32::pac::RCC.apb2enr().read().spi1en()); +fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; - // test rx-only configuration - let mut spi = Spi::new_blocking_rxonly(&mut spi_peri, &mut sck, &mut miso, spi_config); - let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); mosi_out.set_high(); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); mosi_out.set_low(); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0x00; 9]); + assert_eq!(buf, [W::default(); 9]); spi.blocking_read::(&mut []).unwrap(); spi.blocking_read::(&mut []).unwrap(); - drop(mosi_out); - drop(spi); +} + +fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Blocking>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. - let mut spi = Spi::new_blocking_txonly(&mut spi_peri, &mut sck, &mut mosi, spi_config); - spi.blocking_transfer(&mut buf, &data).unwrap(); - spi.blocking_transfer_in_place(&mut buf).unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.blocking_read(&mut buf).unwrap(); - spi.blocking_transfer::(&mut [], &[]).unwrap(); - spi.blocking_transfer_in_place::(&mut []).unwrap(); - spi.blocking_read::(&mut []).unwrap(); - spi.blocking_write::(&[]).unwrap(); - drop(spi); - - // Test tx-only nosck. - let mut spi = Spi::new_blocking_txonly_nosck(&mut spi_peri, &mut mosi, spi_config); spi.blocking_write(&buf).unwrap(); spi.blocking_write::(&[]).unwrap(); spi.blocking_write(&buf).unwrap(); - drop(spi); - - info!("Test OK"); - cortex_m::asm::bkpt(); + spi.blocking_write::(&[]).unwrap(); } diff --git a/tests/stm32/src/bin/spi_dma.rs b/tests/stm32/src/bin/spi_dma.rs index a1cbc0ed1..b4fdb8faa 100644 --- a/tests/stm32/src/bin/spi_dma.rs +++ b/tests/stm32/src/bin/spi_dma.rs @@ -7,7 +7,8 @@ use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; -use embassy_stm32::spi::{self, Spi}; +use embassy_stm32::mode::Async; +use embassy_stm32::spi::{self, Spi, Word}; use embassy_stm32::time::Hertz; #[embassy_executor::main] @@ -26,20 +27,76 @@ async fn main(_spawner: Spawner) { spi_config.frequency = Hertz(1_000_000); let mut spi = Spi::new( - &mut spi_peri, - &mut sck, // Arduino D13 - &mut mosi, // Arduino D11 - &mut miso, // Arduino D12 - &mut tx_dma, - &mut rx_dma, + spi_peri.reborrow(), + sck.reborrow(), // Arduino D13 + mosi.reborrow(), // Arduino D11 + miso.reborrow(), // Arduino D12 + tx_dma.reborrow(), + rx_dma.reborrow(), spi_config, ); - let data: [u8; 9] = [0x00, 0xFF, 0xAA, 0x55, 0xC0, 0xFF, 0xEE, 0xC0, 0xDE]; + test_txrx::(&mut spi).await; + test_txrx::(&mut spi).await; + drop(spi); + + // test rx-only configuration + let mut spi = Spi::new_rxonly( + spi_peri.reborrow(), + sck.reborrow(), + miso.reborrow(), + // SPIv1/f1 requires txdma even if rxonly. + #[cfg(not(feature = "spi-v345"))] + tx_dma.reborrow(), + rx_dma.reborrow(), + spi_config, + ); + let mut mosi_out = Output::new(mosi.reborrow(), Level::Low, Speed::VeryHigh); + + test_rx::(&mut spi, &mut mosi_out).await; + test_rx::(&mut spi, &mut mosi_out).await; + drop(spi); + drop(mosi_out); + + let mut spi = Spi::new_txonly( + spi_peri.reborrow(), + sck.reborrow(), + mosi.reborrow(), + tx_dma.reborrow(), + spi_config, + ); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + let mut spi = Spi::new_txonly_nosck(spi_peri.reborrow(), mosi.reborrow(), tx_dma.reborrow(), spi_config); + test_tx::(&mut spi).await; + test_tx::(&mut spi).await; + drop(spi); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +async fn test_txrx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let data: [W; 9] = [ + 0x00u8.into(), + 0xFFu8.into(), + 0xAAu8.into(), + 0x55u8.into(), + 0xC0u8.into(), + 0xFFu8.into(), + 0xEEu8.into(), + 0xC0u8.into(), + 0xDEu8.into(), + ]; // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. // so we should get the data we sent back. - let mut buf = [0; 9]; + let mut buf = [W::default(); 9]; spi.transfer(&mut buf, &data).await.unwrap(); assert_eq!(buf, data); @@ -83,44 +140,41 @@ async fn main(_spawner: Spawner) { spi.blocking_write(&buf).unwrap(); spi.blocking_read(&mut buf).unwrap(); spi.write(&buf).await.unwrap(); +} - core::mem::drop(spi); +async fn test_rx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>, mosi_out: &mut Output<'_>) +where + W: core::ops::Not, +{ + let mut buf = [W::default(); 9]; - // test rx-only configuration - let mut spi = Spi::new_rxonly( - &mut spi_peri, - &mut sck, - &mut miso, - // SPIv1/f1 requires txdma even if rxonly. - #[cfg(not(feature = "spi-v345"))] - &mut tx_dma, - &mut rx_dma, - spi_config, - ); - let mut mosi_out = Output::new(&mut mosi, Level::Low, Speed::VeryHigh); mosi_out.set_high(); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); spi.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, [0xff; 9]); + assert_eq!(buf, [!W::default(); 9]); mosi_out.set_low(); spi.read(&mut buf).await.unwrap(); - assert_eq!(buf, [0x00; 9]); + assert_eq!(buf, [W::default(); 9]); spi.read::(&mut []).await.unwrap(); spi.blocking_read::(&mut []).unwrap(); - drop(mosi_out); - drop(spi); +} + +async fn test_tx + defmt::Format + Eq>(spi: &mut Spi<'_, Async>) +where + W: core::ops::Not, +{ + let buf = [W::default(); 9]; // Test tx-only. Just check it doesn't hang, not much else we can do without using SPI slave. - let mut spi = Spi::new_txonly(&mut spi_peri, &mut sck, &mut mosi, &mut tx_dma, spi_config); spi.blocking_write(&buf).unwrap(); spi.write(&buf).await.unwrap(); spi.blocking_write(&buf).unwrap(); @@ -129,20 +183,4 @@ async fn main(_spawner: Spawner) { spi.write(&buf).await.unwrap(); spi.write::(&[]).await.unwrap(); spi.blocking_write::(&[]).unwrap(); - drop(spi); - - // Test tx-only nosck. - let mut spi = Spi::new_txonly_nosck(&mut spi_peri, &mut mosi, &mut tx_dma, spi_config); - spi.blocking_write(&buf).unwrap(); - spi.write(&buf).await.unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.blocking_write(&buf).unwrap(); - spi.write(&buf).await.unwrap(); - spi.write(&buf).await.unwrap(); - spi.write::(&[]).await.unwrap(); - spi.blocking_write::(&[]).unwrap(); - drop(spi); - - info!("Test OK"); - cortex_m::asm::bkpt(); } diff --git a/tests/stm32/src/bin/ucpd.rs b/tests/stm32/src/bin/ucpd.rs index a6d13b34a..97aefe1a0 100644 --- a/tests/stm32/src/bin/ucpd.rs +++ b/tests/stm32/src/bin/ucpd.rs @@ -9,7 +9,7 @@ use defmt::{assert, assert_eq}; use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_stm32::ucpd::{self, CcPhy, CcPull, CcSel, CcVState, RxError, Ucpd}; -use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::{bind_interrupts, peripherals, Peri}; use embassy_time::Timer; bind_interrupts!(struct Irqs { @@ -28,8 +28,8 @@ async fn wait_for_vstate(cc_phy: &mut CcPhy<'_, T>, vstate: C async fn source( mut ucpd: Ucpd<'static, peripherals::UCPD1>, - rx_dma: peripherals::DMA1_CH1, - tx_dma: peripherals::DMA1_CH2, + rx_dma: Peri<'static, peripherals::DMA1_CH1>, + tx_dma: Peri<'static, peripherals::DMA1_CH2>, ) { debug!("source: setting default current pull-up"); ucpd.cc_phy().set_pull(CcPull::SourceDefaultUsb); @@ -65,8 +65,8 @@ async fn source( async fn sink( mut ucpd: Ucpd<'static, peripherals::UCPD2>, - rx_dma: peripherals::DMA1_CH3, - tx_dma: peripherals::DMA1_CH4, + rx_dma: Peri<'static, peripherals::DMA1_CH3>, + tx_dma: Peri<'static, peripherals::DMA1_CH4>, ) { debug!("sink: setting pull down"); ucpd.cc_phy().set_pull(CcPull::Sink); @@ -106,8 +106,8 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); // Wire between PD0 and PA8 - let ucpd1 = Ucpd::new(p.UCPD1, Irqs {}, p.PA8, p.PB15); - let ucpd2 = Ucpd::new(p.UCPD2, Irqs {}, p.PD0, p.PD2); + let ucpd1 = Ucpd::new(p.UCPD1, Irqs {}, p.PA8, p.PB15, Default::default()); + let ucpd2 = Ucpd::new(p.UCPD2, Irqs {}, p.PD0, p.PD2, Default::default()); join( source(ucpd1, p.DMA1_CH1, p.DMA1_CH2), diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs index 53da30fff..129c7b692 100644 --- a/tests/stm32/src/bin/usart.rs +++ b/tests/stm32/src/bin/usart.rs @@ -22,7 +22,7 @@ async fn main(_spawner: Spawner) { { let config = Config::default(); - let mut usart = Uart::new_blocking(&mut usart, &mut rx, &mut tx, config).unwrap(); + let mut usart = Uart::new_blocking(usart.reborrow(), rx.reborrow(), tx.reborrow(), config).unwrap(); // We can't send too many bytes, they have to fit in the FIFO. // This is because we aren't sending+receiving at the same time. @@ -33,12 +33,19 @@ async fn main(_spawner: Spawner) { let mut buf = [0; 2]; usart.blocking_read(&mut buf).unwrap(); assert_eq!(buf, data); + + // Test flush doesn't hang. + usart.blocking_write(&data).unwrap(); + usart.blocking_flush().unwrap(); + + // Test flush doesn't hang if there's nothing to flush + usart.blocking_flush().unwrap(); } // Test error handling with with an overflow error { let config = Config::default(); - let mut usart = Uart::new_blocking(&mut usart, &mut rx, &mut tx, config).unwrap(); + let mut usart = Uart::new_blocking(usart.reborrow(), rx.reborrow(), tx.reborrow(), config).unwrap(); // Send enough bytes to fill the RX FIFOs off all USART versions. let data = [0; 64]; @@ -68,7 +75,7 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.baudrate = baudrate; - let mut usart = match Uart::new_blocking(&mut usart, &mut rx, &mut tx, config) { + let mut usart = match Uart::new_blocking(usart.reborrow(), rx.reborrow(), tx.reborrow(), config) { Ok(x) => x, Err(ConfigError::BaudrateTooHigh) => { info!("baudrate too high"); diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs index 266b81809..a34498376 100644 --- a/tests/stm32/src/bin/usart_dma.rs +++ b/tests/stm32/src/bin/usart_dma.rs @@ -51,6 +51,23 @@ async fn main(_spawner: Spawner) { assert_eq!(tx_buf, rx_buf); } + // Test flush doesn't hang. Check multiple combinations of async+blocking. + tx.write(&tx_buf).await.unwrap(); + tx.flush().await.unwrap(); + tx.flush().await.unwrap(); + + tx.write(&tx_buf).await.unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.blocking_flush().unwrap(); + tx.flush().await.unwrap(); + + tx.blocking_write(&tx_buf).unwrap(); + tx.flush().await.unwrap(); + tx.blocking_flush().unwrap(); + 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 98c7ef312..83c0887ac 100644 --- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -43,8 +43,7 @@ async fn main(spawner: Spawner) { let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config).unwrap(); let (tx, rx) = usart.split(); static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE]; - let dma_buf = unsafe { DMA_BUF.as_mut() }; - let rx = rx.into_ring_buffered(dma_buf); + let rx = rx.into_ring_buffered(unsafe { &mut *core::ptr::addr_of_mut!(DMA_BUF) }); info!("Spawning tasks"); spawner.spawn(transmit_task(tx)).unwrap(); diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs index 935a41ed2..829f2cff0 100644 --- a/tests/stm32/src/common.rs +++ b/tests/stm32/src/common.rs @@ -284,7 +284,9 @@ pub fn config() -> Config { #[cfg(feature = "stm32g071rb")] { - config.rcc.hsi = true; + config.rcc.hsi = Some(Hsi { + sys_div: HsiSysDiv::DIV1, + }); config.rcc.pll = Some(Pll { source: PllSource::HSI, prediv: PllPreDiv::DIV1, diff --git a/tests/utils/Cargo.toml b/tests/utils/Cargo.toml index 7b54a4f52..bda55ad32 100644 --- a/tests/utils/Cargo.toml +++ b/tests/utils/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -rand = "0.8" +rand = "0.9" serial = "0.4"