diff --git a/.github/ci/test.sh b/.github/ci/test.sh
index c78865e54..c9b332cf8 100755
--- a/.github/ci/test.sh
+++ b/.github/ci/test.sh
@@ -29,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/README.md b/README.md
index 383fb6671..669fa469b 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +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, nRF54 and nRF91 series.
- - embassy-rp, for the Raspberry Pi RP2040 and RP23xx microcontrollers.
- - embassy-mspm0, for the Texas Instruments MSPM0 microcontrollers.
- - esp-rs, for the Espressif Systems ESP32 series of chips.
- - Embassy HAL support for Espressif chips, as well as Async WiFi, Bluetooth and ESP-NOW, is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository.
- - ch32-hal, for the WCH 32-bit RISC-V(CH32V) series of chips.
- - mpfs-hal, for the Microchip PolarFire SoC.
- - py32-hal, for the Puya Semiconductor PY32 series of microcontrollers.
+- **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.
+
+- **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.
- **Bluetooth**
- - The trouble crate provides a Bluetooth Low Energy 4.x and 5.x Host that runs on any microcontroller implementing the bt-hci traits (currently `nRF52`, `rp2040`, `rp23xx` and `esp32` and `serial` controllers are supported).
- - 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.
+ - 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.
-- **LoRa** - The lora-rs project provides an async LoRa and LoRaWAN stack that works well on Embassy.
+- **LoRa** -
+ The [lora-rs](https://github.com/lora-rs/lora-rs) project provides an async LoRa and LoRaWAN stack that works well on Embassy.
-- **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.
-
-- **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
@@ -93,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
@@ -126,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
@@ -136,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.sh b/ci.sh
index 5a438f0b1..f2d461c1a 100755
--- a/ci.sh
+++ b/ci.sh
@@ -19,7 +19,7 @@ fi
TARGET=$(rustc -vV | sed -n 's|host: ||p')
BUILD_EXTRA=""
-if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then
+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
@@ -88,16 +88,17 @@ cargo batch \
--- 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-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,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-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 \
@@ -153,10 +154,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 \
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/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/pages/faq.adoc b/docs/pages/faq.adoc
index a535e89f8..b21be9a30 100644
--- a/docs/pages/faq.adoc
+++ b/docs/pages/faq.adoc
@@ -268,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.
diff --git a/docs/pages/getting_started.adoc b/docs/pages/getting_started.adoc
index 954f3fd28..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!
────────────────────────────────────────────────────────────────────────────────
diff --git a/docs/pages/imxrt.adoc b/docs/pages/imxrt.adoc
index adf5218e8..bbd65e494 100644
--- a/docs/pages/imxrt.adoc
+++ b/docs/pages/imxrt.adoc
@@ -9,5 +9,6 @@ The link: link:https://github.com/embassy-rs/embassy/tree/main/embassy-imxrt[Emb
The following peripherals have a HAL implementation at present
+* CRC
* GPIO
-
+* RNG
diff --git a/docs/pages/new_project.adoc b/docs/pages/new_project.adoc
index af1cb75c5..cd943b4f6 100644
--- a/docs/pages/new_project.adoc
+++ b/docs/pages/new_project.adoc
@@ -150,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/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/embassy-imxrt/Cargo.toml b/embassy-imxrt/Cargo.toml
index d58de6353..f16002a8d 100644
--- a/embassy-imxrt/Cargo.toml
+++ b/embassy-imxrt/Cargo.toml
@@ -12,7 +12,7 @@ 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"]
+features = ["defmt", "unstable-pac", "time", "time-driver-os-timer"]
flavors = [
{ regex_feature = "mimxrt6.*", target = "thumbv8m.main-none-eabihf" }
]
@@ -37,9 +37,12 @@ defmt = ["dep:defmt", "embassy-hal-internal/defmt", "embassy-sync/defmt", "mimxr
time = ["dep:embassy-time", "embassy-embedded-hal/time"]
## Enable custom embassy time-driver implementation, using 32KHz RTC
-time-driver-rtc = ["_time-driver"]
+time-driver-rtc = ["_time-driver", "embassy-time-driver?/tick-hz-1_000"]
-_time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000", "dep:embassy-time-queue-utils", "embassy-embedded-hal/time"]
+## 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 = []
diff --git a/embassy-imxrt/src/clocks.rs b/embassy-imxrt/src/clocks.rs
index 1d36fb142..39c3e6238 100644
--- a/embassy-imxrt/src/clocks.rs
+++ b/embassy-imxrt/src/clocks.rs
@@ -1,8 +1,6 @@
//! Clock configuration for the `RT6xx`
use core::sync::atomic::{AtomicU32, AtomicU8, Ordering};
-#[cfg(feature = "defmt")]
-use defmt;
use paste::paste;
use crate::pac;
@@ -503,7 +501,6 @@ impl ConfigurableClock for LposcConfig {
}
}
} else {
- error!("failed to convert desired clock rate, {:#}, to LPOSC Freq", freq);
Err(ClockError::InvalidFrequency)
}
}
@@ -549,7 +546,6 @@ impl ConfigurableClock for FfroConfig {
Ok(())
}
fn get_clock_rate(&self) -> Result {
- trace!("getting ffro clock rate");
Ok(self.freq.load(Ordering::Relaxed))
}
fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> {
@@ -616,7 +612,6 @@ impl ConfigurableClock for SfroConfig {
fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> {
if self.state == State::Enabled {
if freq == SFRO_FREQ {
- trace!("Sfro frequency is already set at 16MHz");
Ok(())
} else {
Err(ClockError::InvalidFrequency)
@@ -677,7 +672,6 @@ impl MultiSourceClock for MainPllClkConfig {
}
MainPllClkSrc::SFRO => {
if !clock_src_config.is_enabled() {
- error!("Can't set SFRO as source for MainPll as it's not enabled");
return Err(ClockError::ClockNotEnabled);
}
// check if desired frequency is a valid multiple of 16m SFRO clock
@@ -703,7 +697,6 @@ impl ConfigurableClock for MainPllClkConfig {
}
fn disable(&self) -> Result<(), ClockError> {
if self.is_enabled() {
- error!("Attempting to reset the Main Pll Clock, should be resetting its source");
Err(ClockError::ClockNotSupported)
} else {
Err(ClockError::ClockNotEnabled)
@@ -719,7 +712,6 @@ impl ConfigurableClock for MainPllClkConfig {
}
fn set_clock_rate(&mut self, div: u8, mult: u8, freq: u32) -> Result<(), ClockError> {
if self.is_enabled() {
- trace!("attempting to set main pll clock rate");
// SAFETY: unsafe needed to take pointers to Sysctl0 and Clkctl0
let clkctl0 = unsafe { crate::pac::Clkctl0::steal() };
let sysctl0 = unsafe { crate::pac::Sysctl0::steal() };
@@ -741,15 +733,12 @@ impl ConfigurableClock for MainPllClkConfig {
base_rate = r;
}
MainPllClkSrc::FFRO => {
- trace!("found FFRO as source, wait a bit");
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),
}
- trace!("found ffro rate to be: {:#}", base_rate);
if div == 2 {
- trace!("dividing FFRO rate by 2");
clkctl0.syspll0clksel().write(|w| w.sel().ffro_div_2());
delay_loop_clocks(150, desired_freq);
base_rate /= 2;
@@ -763,10 +752,8 @@ impl ConfigurableClock for MainPllClkConfig {
}
};
base_rate *= u32::from(mult);
- trace!("calculated base rate at: {:#}", base_rate);
if base_rate != freq {
// make sure to power syspll back up before returning the error
- error!("invalid frequency found, powering syspll back up before returning error. Check div and mult");
// Clear System PLL reset
clkctl0.syspll0ctl0().write(|w| w.reset().normal());
// Power up SYSPLL
@@ -775,13 +762,11 @@ impl ConfigurableClock for MainPllClkConfig {
.write(|w| w.syspllana_pd().clr_pdruncfg0().syspllldo_pd().clr_pdruncfg0());
return Err(ClockError::InvalidFrequency);
}
- trace!("setting default num and denom");
// 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);
- trace!("setting self.mult as: {:#}", mult);
match mult {
16 => {
clkctl0.syspll0ctl0().modify(|_r, w| w.mult().div_16());
@@ -803,7 +788,6 @@ impl ConfigurableClock for MainPllClkConfig {
}
_ => return Err(ClockError::InvalidMult),
}
- trace!("clear syspll reset");
// Clear System PLL reset
clkctl0.syspll0ctl0().modify(|_r, w| w.reset().normal());
// Power up SYSPLL
@@ -819,7 +803,6 @@ impl ConfigurableClock for MainPllClkConfig {
clkctl0.syspll0ctl0().modify(|_, w| w.holdringoff_ena().dsiable());
delay_loop_clocks(15, desired_freq);
- trace!("setting new PFD0 bits");
// gate the output and clear bits.
// SAFETY: unsafe needed to write the bits for pfd0
clkctl0
@@ -833,7 +816,6 @@ impl ConfigurableClock for MainPllClkConfig {
.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);
- trace!("waiting for mainpll clock to be ready");
while clkctl0.syspll0pfd().read().pfd0_clkrdy().bit_is_clear() {}
// clear by writing a 1
clkctl0.syspll0pfd().modify(|_, w| w.pfd0_clkrdy().set_bit());
@@ -854,11 +836,9 @@ impl ConfigurableClock for MainPllClkConfig {
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 {
- trace!("calculating mult for {:#} / {:#}", rate, base_freq);
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;
- trace!("verifying that calculated mult {:#} is a valid one", mult);
if VALIDMULTS.into_iter().any(|i| i == mult) {
Ok(mult)
} else {
@@ -1112,7 +1092,6 @@ impl ConfigurableClock for MainClkConfig {
Ok(())
}
fn disable(&self) -> Result<(), ClockError> {
- error!("Attempting to reset the main clock, should NOT happen during runtime");
Err(ClockError::ClockNotSupported)
}
fn get_clock_rate(&self) -> Result {
@@ -1120,7 +1099,6 @@ impl ConfigurableClock for MainClkConfig {
Ok(rate)
}
fn set_clock_rate(&mut self, _div: u8, _mult: u8, _freq: u32) -> Result<(), ClockError> {
- error!("The multi-source set_clock_rate_and_source method should be used instead of set_clock_rate");
Err(ClockError::ClockNotSupported)
}
fn is_enabled(&self) -> bool {
@@ -1145,7 +1123,6 @@ impl ConfigurableClock for ClkInConfig {
}
}
fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> {
- trace!("Setting value of clk in config, this won't change the clock itself");
self.freq.as_ref().unwrap().store(freq, Ordering::Relaxed);
Ok(())
}
@@ -1188,7 +1165,6 @@ impl ConfigurableClock for RtcClkConfig {
Ok(())
}
fn disable(&self) -> Result<(), ClockError> {
- error!("Resetting the RTC clock, this should NOT happen during runtime");
Err(ClockError::ClockNotSupported)
}
fn set_clock_rate(&mut self, _div: u8, _mult: u8, freq: u32) -> Result<(), ClockError> {
@@ -1199,7 +1175,6 @@ impl ConfigurableClock for RtcClkConfig {
match r {
RtcFreq::Default1Hz => {
if rtc.ctrl().read().rtc_en().is_enable() {
- trace!("Attempting to enable an already enabled clock, RTC 1Hz");
} else {
rtc.ctrl().modify(|_r, w| w.rtc_en().enable());
}
@@ -1207,7 +1182,6 @@ impl ConfigurableClock for RtcClkConfig {
}
RtcFreq::HighResolution1khz => {
if rtc.ctrl().read().rtc1khz_en().is_enable() {
- trace!("Attempting to enable an already enabled clock, RTC 1Hz");
} else {
rtc.ctrl().modify(|_r, w| w.rtc1khz_en().enable());
}
@@ -1215,7 +1189,6 @@ impl ConfigurableClock for RtcClkConfig {
}
RtcFreq::SubSecond32kHz => {
if rtc.ctrl().read().rtc_subsec_ena().is_enable() {
- trace!("Attempting to enable an already enabled clock, RTC 1Hz");
} else {
rtc.ctrl().modify(|_r, w| w.rtc_subsec_ena().enable());
}
@@ -1245,18 +1218,12 @@ impl ConfigurableClock for RtcClkConfig {
impl SysClkConfig {
/// Updates the system core clock frequency, SW concept used for systick
- fn update_sys_core_clock(&self) {
- trace!(
- "System core clock has been updated to {:?}, this involves no HW reg writes",
- self.sysclkfreq.load(Ordering::Relaxed)
- );
- }
+ fn update_sys_core_clock(&self) {}
}
impl ConfigurableClock for SysOscConfig {
fn enable_and_reset(&self) -> Result<(), ClockError> {
if self.state == State::Enabled {
- trace!("SysOsc was already enabled");
return Ok(());
}
@@ -1498,32 +1465,26 @@ impl ClockOutConfig {
/// 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() {
- error!("couldn't Power on OSC for RTC, result: {:?}", e);
return Err(e);
}
if let Err(e) = config.lposc.enable_and_reset() {
- error!("couldn't Power on LPOSC, result: {:?}", e);
return Err(e);
}
if let Err(e) = config.ffro.enable_and_reset() {
- error!("couldn't Power on FFRO, result: {:?}", e);
return Err(e);
}
if let Err(e) = config.sfro.enable_and_reset() {
- error!("couldn't Power on SFRO, result: {:?}", e);
return Err(e);
}
if let Err(e) = config.sys_osc.enable_and_reset() {
- error!("Couldn't enable sys oscillator {:?}", e);
return Err(e);
}
if let Err(e) = config.main_pll_clk.enable_and_reset() {
- error!("Couldn't enable main pll clock {:?}", e);
return Err(e);
}
@@ -1542,7 +1503,6 @@ fn init_clock_hw(config: ClockConfig) -> Result<(), ClockError> {
init_syscpuahb_clk();
if let Err(e) = config.main_clk.enable_and_reset() {
- error!("Couldn't enable main clock {:?}", e);
return Err(e);
}
@@ -1561,7 +1521,8 @@ pub(crate) unsafe fn init(config: ClockConfig) -> Result<(), ClockError> {
///Trait to expose perph clocks
trait SealedSysconPeripheral {
- fn enable_and_reset_perph_clock();
+ fn enable_perph_clock();
+ fn reset_perph();
fn disable_perph_clock();
}
@@ -1574,7 +1535,18 @@ pub trait SysconPeripheral: SealedSysconPeripheral + 'static {}
///
/// Peripheral must not be in use.
pub fn enable_and_reset() {
- T::enable_and_reset_perph_clock();
+ 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`.
@@ -1588,15 +1560,21 @@ pub fn disable() {
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_and_reset_perph_clock() {
+ fn enable_perph_clock() {
// SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1
let cc1 = unsafe { pac::$clkctl::steal() };
- let rc1 = unsafe { pac::$rstctl::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) });
}
@@ -1605,12 +1583,8 @@ macro_rules! impl_perph_clk {
fn disable_perph_clock() {
// SAFETY: unsafe needed to take pointers to Rstctl1 and Clkctl1
let cc1 = unsafe { pac::$clkctl::steal() };
- let rc1 = unsafe { pac::$rstctl::steal() };
paste! {
- // SAFETY: unsafe due to the use of bits()
- rc1.[<$rstreg _set>]().write(|w| unsafe { w.bits(1 << $bit) });
-
// SAFETY: unsafe due to the use of bits()
cc1.[<$clkreg _clr>]().write(|w| unsafe { w.bits(1 << $bit) });
}
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/lib.rs b/embassy-imxrt/src/lib.rs
index 5fbf3244b..b1183d8fc 100644
--- a/embassy-imxrt/src/lib.rs
+++ b/embassy-imxrt/src/lib.rs
@@ -18,11 +18,13 @@ compile_error!(
pub(crate) mod fmt;
pub mod clocks;
+pub mod crc;
pub mod gpio;
pub mod iopctl;
+pub mod rng;
#[cfg(feature = "_time-driver")]
-pub mod rtc;
+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")]
@@ -132,12 +134,10 @@ pub fn init(config: config::Config) -> Peripherals {
error!("unable to initialize Clocks for reason: {:?}", e);
// Panic here?
}
- gpio::init();
}
-
- // init RTC time driver
#[cfg(feature = "_time-driver")]
- rtc::init(config.time_interrupt_priority);
+ time_driver::init(config.time_interrupt_priority);
+ gpio::init();
peripherals
}
diff --git a/embassy-imxrt/src/rng.rs b/embassy-imxrt/src/rng.rs
new file mode 100644
index 000000000..67e7ab65d
--- /dev/null
+++ b/embassy-imxrt/src/rng.rs
@@ -0,0 +1,257 @@
+//! 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 rand_core::{CryptoRng, RngCore};
+
+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());
+ }
+}
+
+impl RngCore for Rng<'_> {
+ fn next_u32(&mut self) -> u32 {
+ let mut bytes = [0u8; 4];
+ block_on(self.async_fill_bytes(&mut bytes)).unwrap();
+ u32::from_ne_bytes(bytes)
+ }
+
+ fn next_u64(&mut self) -> u64 {
+ let mut bytes = [0u8; 8];
+ block_on(self.async_fill_bytes(&mut bytes)).unwrap();
+ u64::from_ne_bytes(bytes)
+ }
+
+ fn fill_bytes(&mut self, dest: &mut [u8]) {
+ block_on(self.async_fill_bytes(dest)).unwrap();
+ }
+
+ fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
+ self.fill_bytes(dest);
+ Ok(())
+ }
+}
+
+impl CryptoRng for Rng<'_> {}
+
+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/rtc.rs b/embassy-imxrt/src/time_driver.rs
similarity index 68%
rename from embassy-imxrt/src/rtc.rs
rename to embassy-imxrt/src/time_driver.rs
index 56a8f7397..c68679d3e 100644
--- a/embassy-imxrt/src/rtc.rs
+++ b/embassy-imxrt/src/time_driver.rs
@@ -1,5 +1,6 @@
-//! RTC Time Driver.
+//! Time Driver.
use core::cell::{Cell, RefCell};
+#[cfg(feature = "time-driver-rtc")]
use core::sync::atomic::{compiler_fence, AtomicU32, Ordering};
use critical_section::CriticalSection;
@@ -8,27 +9,11 @@ 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};
-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
-fn calc_now(period: u32, counter: u32) -> u64 {
- ((period as u64) << 31) + ((counter ^ ((period & 1) << 31)) as u64)
-}
-
struct AlarmState {
timestamp: Cell,
}
@@ -43,6 +28,34 @@ impl AlarmState {
}
}
+#[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,
@@ -51,12 +64,7 @@ struct Rtc {
queue: Mutex>,
}
-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")]
impl Rtc {
/// Access the GPREG0 register to use it as a 31-bit counter.
#[inline]
@@ -219,6 +227,7 @@ impl Rtc {
}
}
+#[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.
@@ -242,13 +251,158 @@ impl Driver for Rtc {
}
}
-#[cfg(feature = "rt")]
+#[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);
+
+ 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());
+ 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-net/CHANGELOG.md b/embassy-net/CHANGELOG.md
index cfee5f3cf..8773772ce 100644
--- a/embassy-net/CHANGELOG.md
+++ b/embassy-net/CHANGELOG.md
@@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
No unreleased changes yet... Quick, go send a PR!
-## 0.7 - 2025-02-14
+## 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
diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs
index 0103fa7ae..99cf29487 100644
--- a/embassy-nrf/src/chips/nrf5340_app.rs
+++ b/embassy-nrf/src/chips/nrf5340_app.rs
@@ -262,6 +262,9 @@ embassy_hal_internal::peripherals! {
PPI_GROUP4,
PPI_GROUP5,
+ // IPC
+ IPC,
+
// GPIO port 0
#[cfg(feature = "lfxo-pins-as-gpio")]
P0_00,
@@ -327,6 +330,8 @@ embassy_hal_internal::peripherals! {
EGU5,
}
+impl_ipc!(IPC, IPC, IPC);
+
impl_usb!(USBD, USBD, USBD);
impl_uarte!(SERIAL0, UARTE0, SERIAL0);
diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs
index 22d33d080..c2932be31 100644
--- a/embassy-nrf/src/chips/nrf5340_net.rs
+++ b/embassy-nrf/src/chips/nrf5340_net.rs
@@ -141,6 +141,9 @@ embassy_hal_internal::peripherals! {
PPI_GROUP4,
PPI_GROUP5,
+ // IPC
+ IPC,
+
// GPIO port 0
P0_00,
P0_01,
@@ -200,6 +203,8 @@ embassy_hal_internal::peripherals! {
EGU0,
}
+impl_ipc!(IPC, IPC, IPC);
+
impl_uarte!(SERIAL0, UARTE0, SERIAL0);
impl_spim!(SERIAL0, SPIM0, SERIAL0);
impl_spis!(SERIAL0, SPIS0, SERIAL0);
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 07ba2f6d4..5bce65a98 100644
--- a/embassy-nrf/src/lib.rs
+++ b/embassy-nrf/src/lib.rs
@@ -88,6 +88,8 @@ pub mod gpiote;
#[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",
diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs
deleted file mode 100644
index d42bbe5f6..000000000
--- a/embassy-nrf/src/radio/ble.rs
+++ /dev/null
@@ -1,394 +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;
-pub use pac::radio::vals::Mode;
-#[cfg(not(feature = "_nrf51"))]
-use pac::radio::vals::Plen as PreambleLength;
-
-use crate::interrupt::typelevel::Interrupt;
-use crate::pac::radio::vals;
-use crate::radio::*;
-pub use crate::radio::{Error, TxPower};
-use crate::util::slice_in_ram_or;
-use crate::Peri;
-
-/// Radio driver.
-pub struct Radio<'d, T: Instance> {
- _p: Peri<'d, T>,
-}
-
-impl<'d, T: Instance> Radio<'d, T> {
- /// Create a new radio driver.
- pub fn new(
- radio: Peri<'d, T>,
- _irq: impl interrupt::typelevel::Binding> + 'd,
- ) -> Self {
- let r = T::regs();
-
- r.pcnf1().write(|w| {
- // It is 0 bytes long in a standard BLE packet
- w.set_statlen(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
- w.set_maxlen(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
- w.set_balen(3); // 3 bytes base address (+ 1 prefix);
- // Configure the endianess
- // For BLE is always little endian (LSB first)
- w.set_endian(vals::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.
- w.set_whiteen(true);
- });
-
- // 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.set_skipaddr(vals::Skipaddr::SKIP);
- // In BLE 24-bit CRC = 3 bytes
- w.set_len(vals::Len::THREE);
- });
-
- // Ch map between 2400 MHZ .. 2500 MHz
- // All modes use this range
- #[cfg(not(feature = "_nrf51"))]
- r.frequency().write(|w| w.set_map(vals::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.set_ready_start(true);
- w.set_end_disable(true);
- });
-
- // 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.set_mode(mode));
-
- #[cfg(not(feature = "_nrf51"))]
- r.pcnf0().write(|w| {
- w.set_plen(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| {
- // Configure S0 to 1 byte length, this will represent the Data/Adv header flags
- w.set_s0len(true);
- // 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
- w.set_lflen(0);
- // Configure the lengh (in bits) of bits in the S1 field. It could be used to represent the CTEInfo for data packages in BLE.
- w.set_s1len(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| w.set_datawhiteiv(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| w.set_frequency((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| w.set_ap0((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_value(access_address << 8);
-
- // Don't match tx address
- r.txaddress().write(|w| w.set_txaddress(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.set_addr0(true);
- w.set_addr1(true);
- w.set_addr2(true);
- w.set_addr3(true);
- w.set_addr4(true);
- });
- }
-
- /// 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| {
- // 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.set_crcpoly(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| w.set_crcinit(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.set_txpower(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_value(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_value(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_value(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.set_end(true));
-
- r.tasks_stop().write_value(1);
-
- r.events_end().write_value(0);
-
- trace!("radio drop: stopped");
- });
-
- // trace!("radio:enable interrupt");
- // Clear some remnant side-effects (TODO: check if this is necessary)
- r.events_end().write_value(0);
-
- // Enable interrupt
- r.intenset().write(|w| w.set_end(true));
-
- 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() == 1 {
- // trace!("radio:end");
- return core::task::Poll::Ready(());
- }
- Poll::Pending
- })
- .await;
-
- compiler_fence(Ordering::SeqCst);
- r.events_end().write_value(0); // 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_value(1);
-
- // Wait until the radio is disabled
- while r.events_disabled().read() == 0 {}
-
- compiler_fence(Ordering::SeqCst);
-
- // Acknowledge it
- r.events_disabled().write_value(0);
- }
- }
-}
-
-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 2f0bcbe04..7f4f8f462 100644
--- a/embassy-nrf/src/radio/ieee802154.rs
+++ b/embassy-nrf/src/radio/ieee802154.rs
@@ -5,10 +5,11 @@ use core::task::Poll;
use embassy_hal_internal::drop::OnDrop;
-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::pac::radio::vals;
+pub use crate::pac::radio::vals::State as RadioState;
use crate::Peri;
/// Default (IEEE compliant) Start of Frame Delimiter
@@ -200,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
@@ -293,7 +294,7 @@ impl<'d, T: Instance> Radio<'d, T> {
r.shorts().write(|_| {});
r.tasks_stop().write_value(1);
loop {
- match state(r) {
+ match r.state().read().state() {
RadioState::DISABLED | RadioState::RX_IDLE => break,
_ => (),
}
diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs
index 982436266..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",
@@ -21,7 +20,6 @@ use core::marker::PhantomData;
use embassy_hal_internal::PeripheralType;
use embassy_sync::waitqueue::AtomicWaker;
-use pac::radio::vals::State as RadioState;
pub use pac::radio::vals::Txpower as TxPower;
use crate::{interrupt, pac};
@@ -82,6 +80,7 @@ macro_rules! impl_radio {
pac::$pac_type
}
+ #[allow(unused)]
fn state() -> &'static crate::radio::State {
static STATE: crate::radio::State = crate::radio::State::new();
&STATE
@@ -99,8 +98,3 @@ 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::Radio) -> RadioState {
- radio.state().read().state()
-}
diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs
index 083b54b99..3d5e841d1 100644
--- a/embassy-nrf/src/twim.rs
+++ b/embassy-nrf/src/twim.rs
@@ -4,7 +4,6 @@
use core::future::{poll_fn, Future};
use core::marker::PhantomData;
-use core::mem::MaybeUninit;
use core::sync::atomic::compiler_fence;
use core::sync::atomic::Ordering::SeqCst;
use core::task::Poll;
@@ -17,7 +16,7 @@ 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::pac::gpio::vals as gpiovals;
@@ -75,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.
@@ -115,16 +114,24 @@ impl interrupt::typelevel::Handler for InterruptHandl
/// TWI driver.
pub struct Twim<'d, T: Instance> {
_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: Peri<'d, T>,
_irq: impl interrupt::typelevel::Binding> + 'd,
sda: Peri<'d, impl GpioPin>,
scl: Peri<'d, impl GpioPin>,
config: Config,
+ tx_ram_buffer: &'d mut [u8],
) -> Self {
let r = T::regs();
@@ -159,7 +166,10 @@ impl<'d, T: Instance> Twim<'d, T> {
// Enable TWIM instance.
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();
@@ -174,21 +184,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],
- ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>,
- ) -> Result<(), Error> {
+ unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> {
let buffer = if slice_in_ram(buffer) {
buffer
} else {
- let ram_buffer = ram_buffer.ok_or(Error::BufferNotInRAM)?;
+ 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 ram_buffer[..buffer.len()];
- // Inline implementation of the nightly API MaybeUninit::copy_from_slice(ram_buffer, buffer)
- let uninit_src: &[MaybeUninit] = unsafe { core::mem::transmute(buffer) };
- ram_buffer.copy_from_slice(uninit_src);
- unsafe { &*(ram_buffer as *const [MaybeUninit] as *const [u8]) }
+ let ram_buffer = &mut self.tx_ram_buffer[..buffer.len()];
+ ram_buffer.copy_from_slice(buffer);
+ &*ram_buffer
};
if buffer.len() > EASY_DMA_SIZE {
@@ -358,7 +364,6 @@ impl<'d, T: Instance> Twim<'d, T> {
&mut self,
address: u8,
operations: &mut [Operation<'_>],
- tx_ram_buffer: Option<&mut [MaybeUninit; FORCE_COPY_BUFFER_SIZE]>,
last_op: Option<&Operation<'_>>,
inten: bool,
) -> Result {
@@ -397,7 +402,7 @@ impl<'d, T: Instance> Twim<'d, T> {
// Set up DMA buffers.
unsafe {
- self.set_tx_buffer(wr_buffer, tx_ram_buffer)?;
+ self.set_tx_buffer(wr_buffer)?;
self.set_rx_buffer(rd_buffer)?;
}
@@ -450,7 +455,7 @@ impl<'d, T: Instance> Twim<'d, T> {
{
// Set up DMA buffers.
unsafe {
- self.set_tx_buffer(wr_buffer, tx_ram_buffer)?;
+ self.set_tx_buffer(wr_buffer)?;
self.set_rx_buffer(rd_buffer)?;
}
@@ -472,7 +477,7 @@ impl<'d, T: Instance> Twim<'d, T> {
// Set up DMA buffers.
unsafe {
- self.set_tx_buffer(buffer, tx_ram_buffer)?;
+ self.set_tx_buffer(buffer)?;
}
// Start write operation.
@@ -539,28 +544,9 @@ impl<'d, T: Instance> Twim<'d, T> {
/// 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 tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE];
let mut last_op = None;
while !operations.is_empty() {
- let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), 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(())
- }
-
- /// Same as [`blocking_transaction`](Twim::blocking_transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
- pub fn blocking_transaction_from_ram(
- &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, None, last_op, false)?;
+ 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)?;
@@ -580,30 +566,9 @@ impl<'d, T: Instance> Twim<'d, T> {
mut operations: &mut [Operation<'_>],
timeout: Duration,
) -> Result<(), Error> {
- let mut tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE];
let mut last_op = None;
while !operations.is_empty() {
- let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), 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(())
- }
-
- /// Same as [`blocking_transaction_timeout`](Twim::blocking_transaction_timeout) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
- #[cfg(feature = "time")]
- pub fn blocking_transaction_from_ram_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, None, last_op, false)?;
+ 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)?;
@@ -624,28 +589,9 @@ impl<'d, T: Instance> Twim<'d, T> {
/// 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 tx_ram_buffer = [MaybeUninit::uninit(); FORCE_COPY_BUFFER_SIZE];
let mut last_op = None;
while !operations.is_empty() {
- let ops = self.setup_operations(address, operations, Some(&mut tx_ram_buffer), 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(())
- }
-
- /// Same as [`transaction`](Twim::transaction) but will fail instead of copying data into RAM. Consult the module level documentation to learn more.
- pub async fn transaction_from_ram(
- &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, None, last_op, true)?;
+ 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)?;
@@ -665,11 +611,6 @@ impl<'d, T: Instance> Twim<'d, T> {
self.blocking_transaction(address, &mut [Operation::Write(buffer)])
}
- /// 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.blocking_transaction_from_ram(address, &mut [Operation::Write(buffer)])
- }
-
/// Read from an I2C slave.
///
/// The buffer must have a length of at most 255 bytes on the nRF52832
@@ -687,16 +628,6 @@ impl<'d, T: Instance> Twim<'d, T> {
self.blocking_transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
}
- /// 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.blocking_transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
- }
-
// ===========================================
/// Write to an I2C slave with timeout.
@@ -707,17 +638,6 @@ impl<'d, T: Instance> Twim<'d, T> {
self.blocking_transaction_timeout(address, &mut [Operation::Write(buffer)], timeout)
}
- /// 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.blocking_transaction_from_ram_timeout(address, &mut [Operation::Write(buffer)], timeout)
- }
-
/// Read from an I2C slave.
///
/// The buffer must have a length of at most 255 bytes on the nRF52832
@@ -747,22 +667,6 @@ impl<'d, T: Instance> Twim<'d, T> {
)
}
- /// 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.blocking_transaction_from_ram_timeout(
- address,
- &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)],
- timeout,
- )
- }
-
// ===========================================
/// Read from an I2C slave.
@@ -781,12 +685,6 @@ impl<'d, T: Instance> Twim<'d, T> {
self.transaction(address, &mut [Operation::Write(buffer)]).await
}
- /// 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.transaction_from_ram(address, &mut [Operation::Write(buffer)])
- .await
- }
-
/// Write data to an I2C slave, then read data from the slave without
/// triggering a stop condition between the two.
///
@@ -796,17 +694,6 @@ impl<'d, T: Instance> Twim<'d, T> {
self.transaction(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
.await
}
-
- /// 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.transaction_from_ram(address, &mut [Operation::Write(wr_buffer), Operation::Read(rd_buffer)])
- .await
- }
}
impl<'a, T: Instance> Drop for Twim<'a, T> {
@@ -904,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)
}
diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml
index b440591cf..8fb8a50fd 100644
--- a/embassy-rp/Cargo.toml
+++ b/embassy-rp/Cargo.toml
@@ -26,7 +26,10 @@ 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.
diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs
index 67aa5e540..857877680 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;
@@ -18,6 +81,19 @@ use crate::{pac, reset, Peri};
// 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,
@@ -26,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,
@@ -42,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),
@@ -65,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 {
@@ -89,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 {
@@ -152,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,
}
@@ -192,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.into())),
- // 1 => self.gpin1 = Some((hz, gpin.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,
@@ -251,6 +658,7 @@ pub struct XoscConfig {
}
/// PLL configuration.
+#[derive(Clone, Copy, Debug)]
pub struct PllConfig {
/// Reference divisor.
pub refdiv: u8,
@@ -262,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.
@@ -273,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,
@@ -280,6 +733,7 @@ pub enum RefClkSrc {
Rosc,
/// PLL USB.
PllUsb,
+ // See above re gpin handling being commented out
// Gpin0,
// Gpin1,
}
@@ -287,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,
@@ -298,6 +753,7 @@ pub enum SysClkSrc {
Rosc,
/// XOSC.
Xosc,
+ // See above re gpin handling being commented out
// Gpin0,
// Gpin1,
}
@@ -324,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 _,
@@ -333,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 _ ,
}
@@ -351,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 _,
@@ -360,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 _ ,
}
@@ -378,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.
@@ -388,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 _ ,
}
@@ -405,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:
@@ -447,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
@@ -464,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,
};
@@ -484,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);
@@ -496,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),
}
@@ -540,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),
};
@@ -583,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,
};
@@ -608,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,
};
@@ -631,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,
};
@@ -659,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,
};
@@ -725,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)
// }
@@ -773,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));
@@ -783,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.
@@ -906,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.
@@ -1001,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(),
@@ -1009,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!(),
};
@@ -1069,6 +1804,7 @@ impl rand_core::RngCore for RoscRng {
dest.fill_with(Self::next_u8)
}
}
+
/// 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
@@ -1197,3 +1933,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/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
index 5846a8027..546c85a89 100644
--- a/embassy-rp/src/pio_programs/hd44780.rs
+++ b/embassy-rp/src/pio_programs/hd44780.rs
@@ -5,6 +5,7 @@ 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>)
@@ -134,7 +135,10 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
let mut cfg = Config::default();
cfg.use_program(&word_prg.prg, &[&e]);
- cfg.clock_divider = 125u8.into();
+
+ // 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,
@@ -160,7 +164,10 @@ impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> {
let mut cfg = Config::default();
cfg.use_program(&seq_prg.prg, &[&e]);
- cfg.clock_divider = 8u8.into(); // ~64ns/insn
+
+ // 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]);
diff --git a/embassy-rp/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs
index 74537825b..8eac328b3 100644
--- a/embassy-rp/src/pio_programs/mod.rs
+++ b/embassy-rp/src/pio_programs/mod.rs
@@ -1,5 +1,6 @@
//! Pre-built pio programs for common interfaces
+pub mod clock_divider;
pub mod hd44780;
pub mod i2s;
pub mod onewire;
diff --git a/embassy-rp/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs
index e520da8a3..70b3795e9 100644
--- a/embassy-rp/src/pio_programs/rotary_encoder.rs
+++ b/embassy-rp/src/pio_programs/rotary_encoder.rs
@@ -1,11 +1,10 @@
//! PIO backed quadrature encoder
-use fixed::traits::ToFixed;
-
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.
@@ -48,7 +47,10 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> {
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();
+
+ // 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);
diff --git a/embassy-rp/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs
index 495191659..0e9a8daf9 100644
--- a/embassy-rp/src/pio_programs/stepper.rs
+++ b/embassy-rp/src/pio_programs/stepper.rs
@@ -2,11 +2,8 @@
use core::mem::{self, MaybeUninit};
-use fixed::traits::ToFixed;
-use fixed::types::extra::U8;
-use fixed::FixedU32;
-
use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine};
+use crate::pio_programs::clock_divider::calculate_pio_clock_divider;
use crate::Peri;
/// This struct represents a Stepper driver program loaded into pio instruction memory.
@@ -64,7 +61,9 @@ 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.clock_divider = calculate_pio_clock_divider(100 * 136);
+
cfg.use_program(&program.prg, &[]);
sm.set_config(&cfg);
sm.set_enable(true);
@@ -73,9 +72,11 @@ impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, 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");
+ 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();
}
diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs
index da18138b5..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,61 +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(
+ pub fn new<'d, T: Instance>(
_uart: Peri<'d, T>,
tx: Peri<'d, impl TxPin>,
rx: Peri<'d, impl RxPin>,
- irq: impl Binding>,
+ _irq: impl Binding>,
tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
- super::Uart::<'d, T, Async>::init(Some(tx.into()), Some(rx.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(
+ 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>,
+ _irq: impl Binding>,
tx_buffer: &'d mut [u8],
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
- super::Uart::<'d, T, Async>::init(
+ 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(),
+ },
}
}
@@ -160,68 +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(
+ pub fn new<'d, T: Instance>(
_uart: Peri<'d, T>,
- irq: impl Binding>,
+ _irq: impl Binding>,
rx: Peri<'d, impl RxPin>,
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
- super::Uart::<'d, T, Async>::init(None, Some(rx.into()), None, None, config);
- init_buffers::(irq, None, Some(rx_buffer));
+ 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));
- 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(
+ pub fn new_with_rts<'d, T: Instance>(
_uart: Peri<'d, T>,
- irq: impl Binding>,
+ _irq: impl Binding>,
rx: Peri<'d, impl RxPin>,
rts: Peri<'d, impl RtsPin>,
rx_buffer: &'d mut [u8],
config: Config,
) -> Self {
- super::Uart::<'d, T, Async>::init(None, Some(rx.into()), Some(rts.into()), None, config);
- init_buffers::(irq, None, Some(rx_buffer));
+ 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));
- Self { phantom: PhantomData }
+ Self {
+ info: T::info(),
+ state: T::buffered_state(),
+ }
}
- fn read<'a>(buf: &'a mut [u8]) -> impl Future