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> + 'a - where - T: 'd, - { + fn read<'a>( + info: &'static Info, + state: &'static State, + buf: &'a mut [u8], + ) -> impl Future> + 'a { poll_fn(move |cx| { - if let Poll::Ready(r) = Self::try_read(buf) { + if let Poll::Ready(r) = Self::try_read(info, state, buf) { return Poll::Ready(r); } - T::buffered_state().rx_waker.register(cx.waker()); + state.rx_waker.register(cx.waker()); Poll::Pending }) } - fn get_rx_error() -> Option { - let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed); + fn get_rx_error(state: &State) -> Option { + let errs = state.rx_error.swap(0, Ordering::Relaxed); if errs & RXE_OVERRUN != 0 { Some(Error::Overrun) } else if errs & RXE_BREAK != 0 { @@ -235,15 +255,11 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { } } - fn try_read(buf: &mut [u8]) -> Poll> - where - T: 'd, - { + fn try_read(info: &Info, state: &State, buf: &mut [u8]) -> Poll> { if buf.is_empty() { return Poll::Ready(Ok(0)); } - let state = T::buffered_state(); let mut rx_reader = unsafe { state.rx_buf.reader() }; let n = rx_reader.pop(|data| { let n = data.len().min(buf.len()); @@ -252,7 +268,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { }); let result = if n == 0 { - match Self::get_rx_error() { + match Self::get_rx_error(state) { None => return Poll::Pending, Some(e) => Err(e), } @@ -262,8 +278,7 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { // (Re-)Enable the interrupt to receive more data in case it was // disabled because the buffer was full or errors were detected. - let regs = T::regs(); - regs.uartimsc().write_set(|w| { + info.regs.uartimsc().write_set(|w| { w.set_rxim(true); w.set_rtim(true); }); @@ -274,23 +289,19 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { /// Read from UART RX buffer blocking execution until done. pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result { loop { - match Self::try_read(buf) { + match Self::try_read(self.info, self.state, buf) { Poll::Ready(res) => return res, Poll::Pending => continue, } } } - fn fill_buf<'a>() -> impl Future> - where - T: 'd, - { + fn fill_buf<'a>(state: &'static State) -> impl Future> { poll_fn(move |cx| { - let state = T::buffered_state(); let mut rx_reader = unsafe { state.rx_buf.reader() }; let (p, n) = rx_reader.pop_buf(); let result = if n == 0 { - match Self::get_rx_error() { + match Self::get_rx_error(state) { None => { state.rx_waker.register(cx.waker()); return Poll::Pending; @@ -306,64 +317,70 @@ impl<'d, T: Instance> BufferedUartRx<'d, T> { }) } - fn consume(amt: usize) { - let state = T::buffered_state(); + fn consume(info: &Info, state: &State, amt: usize) { let mut rx_reader = unsafe { state.rx_buf.reader() }; rx_reader.pop_done(amt); // (Re-)Enable the interrupt to receive more data in case it was // disabled because the buffer was full or errors were detected. - let regs = T::regs(); - regs.uartimsc().write_set(|w| { + info.regs.uartimsc().write_set(|w| { w.set_rxim(true); w.set_rtim(true); }); } /// we are ready to read if there is data in the buffer - fn read_ready() -> Result { - let state = T::buffered_state(); + fn read_ready(state: &State) -> Result { Ok(!state.rx_buf.is_empty()) } } -impl<'d, T: Instance> BufferedUartTx<'d, T> { +impl BufferedUartTx { /// Create a new buffered UART TX. - pub fn new( + pub fn new<'d, T: Instance>( _uart: Peri<'d, T>, - irq: impl Binding>, + _irq: impl Binding>, tx: Peri<'d, impl TxPin>, tx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init(Some(tx.into()), None, None, None, config); - init_buffers::(irq, Some(tx_buffer), None); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, None, config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } /// Create a new buffered UART TX with flow control. - pub fn new_with_cts( + pub fn new_with_cts<'d, T: Instance>( _uart: Peri<'d, T>, - irq: impl Binding>, + _irq: impl Binding>, tx: Peri<'d, impl TxPin>, cts: Peri<'d, impl CtsPin>, tx_buffer: &'d mut [u8], config: Config, ) -> Self { - super::Uart::<'d, T, Async>::init(Some(tx.into()), None, None, Some(cts.into()), config); - init_buffers::(irq, Some(tx_buffer), None); + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, Some(cts.into()), config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - Self { phantom: PhantomData } + Self { + info: T::info(), + state: T::buffered_state(), + } } - fn write(buf: &[u8]) -> impl Future> + '_ { + fn write<'d>( + info: &'static Info, + state: &'static State, + buf: &'d [u8], + ) -> impl Future> + 'd { poll_fn(move |cx| { if buf.is_empty() { return Poll::Ready(Ok(0)); } - let state = T::buffered_state(); let mut tx_writer = unsafe { state.tx_buf.writer() }; let n = tx_writer.push(|data| { let n = data.len().min(buf.len()); @@ -379,14 +396,13 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { // FIFO and the number of bytes drops below a threshold. When the // FIFO was empty we have to manually pend the interrupt to shovel // TX data from the buffer into the FIFO. - T::Interrupt::pend(); + info.interrupt.pend(); Poll::Ready(Ok(n)) }) } - fn flush() -> impl Future> { + fn flush(state: &'static State) -> impl Future> { poll_fn(move |cx| { - let state = T::buffered_state(); if !state.tx_buf.is_empty() { state.tx_waker.register(cx.waker()); return Poll::Pending; @@ -403,8 +419,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } loop { - let state = T::buffered_state(); - let mut tx_writer = unsafe { state.tx_buf.writer() }; + let mut tx_writer = unsafe { self.state.tx_buf.writer() }; let n = tx_writer.push(|data| { let n = data.len().min(buf.len()); data[..n].copy_from_slice(&buf[..n]); @@ -416,7 +431,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { // FIFO and the number of bytes drops below a threshold. When the // FIFO was empty we have to manually pend the interrupt to shovel // TX data from the buffer into the FIFO. - T::Interrupt::pend(); + self.info.interrupt.pend(); return Ok(n); } } @@ -425,8 +440,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { loop { - let state = T::buffered_state(); - if state.tx_buf.is_empty() { + if self.state.tx_buf.is_empty() { return Ok(()); } } @@ -434,7 +448,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// Check if UART is busy. pub fn busy(&self) -> bool { - T::regs().uartfr().read().busy() + self.info.regs.uartfr().read().busy() } /// Assert a break condition after waiting for the transmit buffers to empty, @@ -445,7 +459,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { /// This method may block for a long amount of time since it has to wait /// for the transmit fifo to empty, which may take a while on slow links. pub async fn send_break(&mut self, bits: u32) { - let regs = T::regs(); + let regs = self.info.regs; let bits = bits.max({ let lcr = regs.uartlcr_h().read(); let width = lcr.wlen() as u32 + 5; @@ -458,7 +472,7 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { let div_clk = clk_peri_freq() as u64 * 64; let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; - Self::flush().await.unwrap(); + Self::flush(self.state).await.unwrap(); while self.busy() {} regs.uartlcr_h().write_set(|w| w.set_brk(true)); Timer::after_micros(wait_usecs).await; @@ -466,28 +480,26 @@ impl<'d, T: Instance> BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> { +impl Drop for BufferedUartRx { fn drop(&mut self) { - let state = T::buffered_state(); - unsafe { state.rx_buf.deinit() } + unsafe { self.state.rx_buf.deinit() } // TX is inactive if the buffer is not available. // We can now unregister the interrupt handler - if !state.tx_buf.is_available() { - T::Interrupt::disable(); + if !self.state.tx_buf.is_available() { + self.info.interrupt.disable(); } } } -impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { +impl Drop for BufferedUartTx { fn drop(&mut self) { - let state = T::buffered_state(); - unsafe { state.tx_buf.deinit() } + unsafe { self.state.tx_buf.deinit() } // RX is inactive if the buffer is not available. // We can now unregister the interrupt handler - if !state.rx_buf.is_available() { - T::Interrupt::disable(); + if !self.state.rx_buf.is_available() { + self.info.interrupt.disable(); } } } @@ -499,7 +511,7 @@ pub struct BufferedInterruptHandler { impl interrupt::typelevel::Handler for BufferedInterruptHandler { unsafe fn on_interrupt() { - let r = T::regs(); + let r = T::info().regs; if r.uartdmacr().read().rxdmae() { return; } @@ -603,95 +615,95 @@ impl embedded_io::Error for Error { } } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUart<'d, T> { +impl embedded_io_async::ErrorType for BufferedUart { type Error = Error; } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartRx<'d, T> { +impl embedded_io_async::ErrorType for BufferedUartRx { type Error = Error; } -impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartTx<'d, T> { +impl embedded_io_async::ErrorType for BufferedUartTx { type Error = Error; } -impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUart<'d, T> { +impl embedded_io_async::Read for BufferedUart { async fn read(&mut self, buf: &mut [u8]) -> Result { - BufferedUartRx::<'d, T>::read(buf).await + BufferedUartRx::read(self.rx.info, self.rx.state, buf).await } } -impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> { +impl embedded_io_async::Read for BufferedUartRx { async fn read(&mut self, buf: &mut [u8]) -> Result { - Self::read(buf).await + Self::read(self.info, self.state, buf).await } } -impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> { +impl embedded_io_async::ReadReady for BufferedUart { fn read_ready(&mut self) -> Result { - BufferedUartRx::<'d, T>::read_ready() + BufferedUartRx::read_ready(self.rx.state) } } -impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> { +impl embedded_io_async::ReadReady for BufferedUartRx { fn read_ready(&mut self) -> Result { - Self::read_ready() + Self::read_ready(self.state) } } -impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> { +impl embedded_io_async::BufRead for BufferedUart { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - BufferedUartRx::<'d, T>::fill_buf().await + BufferedUartRx::fill_buf(self.rx.state).await } fn consume(&mut self, amt: usize) { - BufferedUartRx::<'d, T>::consume(amt) + BufferedUartRx::consume(self.rx.info, self.rx.state, amt) } } -impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUartRx<'d, T> { +impl embedded_io_async::BufRead for BufferedUartRx { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - Self::fill_buf().await + Self::fill_buf(self.state).await } fn consume(&mut self, amt: usize) { - Self::consume(amt) + Self::consume(self.info, self.state, amt) } } -impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUart<'d, T> { +impl embedded_io_async::Write for BufferedUart { async fn write(&mut self, buf: &[u8]) -> Result { - BufferedUartTx::<'d, T>::write(buf).await + BufferedUartTx::write(self.tx.info, self.tx.state, buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - BufferedUartTx::<'d, T>::flush().await + BufferedUartTx::flush(self.tx.state).await } } -impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUartTx<'d, T> { +impl embedded_io_async::Write for BufferedUartTx { async fn write(&mut self, buf: &[u8]) -> Result { - Self::write(buf).await + Self::write(self.info, self.state, buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - Self::flush().await + Self::flush(self.state).await } } -impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUart<'d, T> { +impl embedded_io::Read for BufferedUart { fn read(&mut self, buf: &mut [u8]) -> Result { self.rx.blocking_read(buf) } } -impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUartRx<'d, T> { +impl embedded_io::Read for BufferedUartRx { fn read(&mut self, buf: &mut [u8]) -> Result { self.blocking_read(buf) } } -impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { +impl embedded_io::Write for BufferedUart { fn write(&mut self, buf: &[u8]) -> Result { self.tx.blocking_write(buf) } @@ -701,7 +713,7 @@ impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { } } -impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { +impl embedded_io::Write for BufferedUartTx { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write(buf) } @@ -711,11 +723,11 @@ impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T> { +impl embedded_hal_02::serial::Read for BufferedUartRx { type Error = Error; fn read(&mut self) -> Result> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -736,7 +748,7 @@ impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T } } -impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d, T> { +impl embedded_hal_02::blocking::serial::Write for BufferedUartTx { type Error = Error; fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { @@ -755,7 +767,7 @@ impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedU } } -impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> { +impl embedded_hal_02::serial::Read for BufferedUart { type Error = Error; fn read(&mut self) -> Result> { @@ -763,7 +775,7 @@ impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> } } -impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUart<'d, T> { +impl embedded_hal_02::blocking::serial::Write for BufferedUart { type Error = Error; fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { @@ -782,25 +794,25 @@ impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedU } } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUartRx { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUartTx { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::ErrorType for BufferedUart { type Error = Error; } -impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> { +impl embedded_hal_nb::serial::Read for BufferedUartRx { fn read(&mut self) -> nb::Result { embedded_hal_02::serial::Read::read(self) } } -impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { +impl embedded_hal_nb::serial::Write for BufferedUartTx { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) } @@ -810,13 +822,13 @@ impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { } } -impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::Read for BufferedUart { fn read(&mut self) -> Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } -impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { +impl embedded_hal_nb::serial::Write for BufferedUart { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) } diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 7ce074a3f..c3a15fda5 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -13,7 +13,8 @@ use pac::uart::regs::Uartris; use crate::clocks::clk_peri_freq; use crate::dma::{AnyChannel, Channel}; use crate::gpio::{AnyPin, SealedPin}; -use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::interrupt::typelevel::{Binding, Interrupt as _}; +use crate::interrupt::{Interrupt, InterruptExt}; use crate::pac::io::vals::{Inover, Outover}; use crate::{interrupt, pac, peripherals, RegExt}; @@ -135,37 +136,41 @@ pub struct DmaState { } /// UART driver. -pub struct Uart<'d, T: Instance, M: Mode> { - tx: UartTx<'d, T, M>, - rx: UartRx<'d, T, M>, +pub struct Uart<'d, M: Mode> { + tx: UartTx<'d, M>, + rx: UartRx<'d, M>, } /// UART TX driver. -pub struct UartTx<'d, T: Instance, M: Mode> { +pub struct UartTx<'d, M: Mode> { + info: &'static Info, tx_dma: Option>, - phantom: PhantomData<(&'d mut T, M)>, + phantom: PhantomData, } /// UART RX driver. -pub struct UartRx<'d, T: Instance, M: Mode> { +pub struct UartRx<'d, M: Mode> { + info: &'static Info, + dma_state: &'static DmaState, rx_dma: Option>, - phantom: PhantomData<(&'d mut T, M)>, + phantom: PhantomData, } -impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { +impl<'d, M: Mode> UartTx<'d, M> { /// Create a new DMA-enabled UART which can only send data - pub fn new( + pub fn new( _uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, tx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - Uart::::init(Some(tx.into()), None, None, None, config); - Self::new_inner(Some(tx_dma.into())) + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner(T::info(), Some(tx_dma.into())) } - fn new_inner(tx_dma: Option>) -> Self { + fn new_inner(info: &'static Info, tx_dma: Option>) -> Self { Self { + info, tx_dma, phantom: PhantomData, } @@ -173,7 +178,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - let r = T::regs(); + let r = self.info.regs; for &b in buffer { while r.uartfr().read().txff() {} r.uartdr().write(|w| w.set_data(b)); @@ -183,14 +188,13 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// Flush UART TX blocking execution until done. pub fn blocking_flush(&mut self) -> Result<(), Error> { - let r = T::regs(); - while !r.uartfr().read().txfe() {} + while !self.info.regs.uartfr().read().txfe() {} Ok(()) } /// Check if UART is busy transmitting. pub fn busy(&self) -> bool { - T::regs().uartfr().read().busy() + self.info.regs.uartfr().read().busy() } /// Assert a break condition after waiting for the transmit buffers to empty, @@ -201,7 +205,7 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { /// This method may block for a long amount of time since it has to wait /// for the transmit fifo to empty, which may take a while on slow links. pub async fn send_break(&mut self, bits: u32) { - let regs = T::regs(); + let regs = self.info.regs; let bits = bits.max({ let lcr = regs.uartlcr_h().read(); let width = lcr.wlen() as u32 + 5; @@ -222,65 +226,80 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { } } -impl<'d, T: Instance> UartTx<'d, T, Blocking> { +impl<'d> UartTx<'d, Blocking> { /// Create a new UART TX instance for blocking mode operations. - pub fn new_blocking(_uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { - Uart::::init(Some(tx.into()), None, None, None, config); - Self::new_inner(None) + pub fn new_blocking(_uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner(T::info(), None) } /// Convert this uart TX instance into a buffered uart using the provided /// irq and transmit buffer. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], - ) -> BufferedUartTx<'d, T> { - buffered::init_buffers::(irq, Some(tx_buffer), None); + ) -> BufferedUartTx { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); - BufferedUartTx { phantom: PhantomData } + BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + } } } -impl<'d, T: Instance> UartTx<'d, T, Async> { +impl<'d> UartTx<'d, Async> { /// Write to UART TX from the provided buffer using DMA. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { let ch = self.tx_dma.as_mut().unwrap().reborrow(); let transfer = unsafe { - T::regs().uartdmacr().write_set(|reg| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_txdmae(true); }); // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ.into()) + crate::dma::write( + ch, + buffer, + self.info.regs.uartdr().as_ptr() as *mut _, + self.info.tx_dreq.into(), + ) }; transfer.await; Ok(()) } } -impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { +impl<'d, M: Mode> UartRx<'d, M> { /// Create a new DMA-enabled UART which can only receive data - pub fn new( + pub fn new( _uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, _irq: impl Binding>, rx_dma: Peri<'d, impl Channel>, config: Config, ) -> Self { - Uart::::init(None, Some(rx.into()), None, None, config); - Self::new_inner(true, Some(rx_dma.into())) + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner(T::info(), T::dma_state(), true, Some(rx_dma.into())) } - fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + fn new_inner( + info: &'static Info, + dma_state: &'static DmaState, + has_irq: bool, + rx_dma: Option>, + ) -> Self { debug_assert_eq!(has_irq, rx_dma.is_some()); if has_irq { // disable all error interrupts initially - T::regs().uartimsc().write(|w| w.0 = 0); - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; + info.regs.uartimsc().write(|w| w.0 = 0); + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; } Self { + info, + dma_state, rx_dma, phantom: PhantomData, } @@ -299,7 +318,7 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { /// encountered. in both cases, `len` is the number of *good* bytes copied into /// `buffer`. fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { - let r = T::regs(); + let r = self.info.regs; for (i, b) in buffer.iter_mut().enumerate() { if r.uartfr().read().rxfe() { return Ok(i); @@ -323,12 +342,12 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { } } -impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { +impl<'d, M: Mode> Drop for UartRx<'d, M> { fn drop(&mut self) { if self.rx_dma.is_some() { - T::Interrupt::disable(); + self.info.interrupt.disable(); // clear dma flags. irq handlers use these to disambiguate among themselves. - T::regs().uartdmacr().write_clear(|reg| { + self.info.regs.uartdmacr().write_clear(|reg| { reg.set_rxdmae(true); reg.set_txdmae(true); reg.set_dmaonerr(true); @@ -337,23 +356,26 @@ impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { } } -impl<'d, T: Instance> UartRx<'d, T, Blocking> { +impl<'d> UartRx<'d, Blocking> { /// Create a new UART RX instance for blocking mode operations. - pub fn new_blocking(_uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { - Uart::::init(None, Some(rx.into()), None, None, config); - Self::new_inner(false, None) + pub fn new_blocking(_uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner(T::info(), T::dma_state(), false, None) } /// Convert this uart RX instance into a buffered uart using the provided /// irq and receive buffer. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, rx_buffer: &'d mut [u8], - ) -> BufferedUartRx<'d, T> { - buffered::init_buffers::(irq, None, Some(rx_buffer)); + ) -> BufferedUartRx { + buffered::init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); - BufferedUartRx { phantom: PhantomData } + BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + } } } @@ -364,7 +386,7 @@ pub struct InterruptHandler { impl interrupt::typelevel::Handler for InterruptHandler { unsafe fn on_interrupt() { - let uart = T::regs(); + let uart = T::info().regs; if !uart.uartdmacr().read().rxdmae() { return; } @@ -380,13 +402,13 @@ impl interrupt::typelevel::Handler for InterruptHandl } } -impl<'d, T: Instance> UartRx<'d, T, Async> { +impl<'d> UartRx<'d, Async> { /// Read from UART RX into the provided buffer. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { // clear error flags before we drain the fifo. errors that have accumulated // in the flags will also be present in the fifo. - T::dma_state().rx_errs.store(0, Ordering::Relaxed); - T::regs().uarticr().write(|w| { + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { w.set_oeic(true); w.set_beic(true); w.set_peic(true); @@ -408,28 +430,33 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // interrupt flags will have been raised, and those will be picked up immediately // by the interrupt handler. let ch = self.rx_dma.as_mut().unwrap().reborrow(); - T::regs().uartimsc().write_set(|w| { + self.info.regs.uartimsc().write_set(|w| { w.set_oeim(true); w.set_beim(true); w.set_peim(true); w.set_feim(true); }); - T::regs().uartdmacr().write_set(|reg| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_rxdmae(true); reg.set_dmaonerr(true); }); let transfer = unsafe { // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - crate::dma::read(ch, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ.into()) + crate::dma::read( + ch, + self.info.regs.uartdr().as_ptr() as *const _, + buffer, + self.info.rx_dreq.into(), + ) }; // wait for either the transfer to complete or an error to happen. let transfer_result = select( transfer, poll_fn(|cx| { - T::dma_state().rx_err_waker.register(cx.waker()); - match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + self.dma_state.rx_err_waker.register(cx.waker()); + match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -441,7 +468,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { Either::First(()) => { // We're here because the DMA finished, BUT if an error occurred on the LAST // byte, then we may still need to grab the error state! - Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + Uartris(self.dma_state.rx_errs.swap(0, Ordering::Relaxed) as u32) } Either::Second(e) => { // We're here because we errored, which means this is the error that @@ -521,8 +548,8 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { ) -> Result { // clear error flags before we drain the fifo. errors that have accumulated // in the flags will also be present in the fifo. - T::dma_state().rx_errs.store(0, Ordering::Relaxed); - T::regs().uarticr().write(|w| { + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { w.set_oeic(true); w.set_beic(true); w.set_peic(true); @@ -555,13 +582,13 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // interrupt flags will have been raised, and those will be picked up immediately // by the interrupt handler. let ch = self.rx_dma.as_mut().unwrap(); - T::regs().uartimsc().write_set(|w| { + self.info.regs.uartimsc().write_set(|w| { w.set_oeim(true); w.set_beim(true); w.set_peim(true); w.set_feim(true); }); - T::regs().uartdmacr().write_set(|reg| { + self.info.regs.uartdmacr().write_set(|reg| { reg.set_rxdmae(true); reg.set_dmaonerr(true); }); @@ -572,9 +599,9 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // is held across an await and makes the future non-Send. crate::dma::read( ch.reborrow(), - T::regs().uartdr().as_ptr() as *const _, + self.info.regs.uartdr().as_ptr() as *const _, sbuffer, - T::RX_DREQ.into(), + self.info.rx_dreq.into(), ) }; @@ -582,8 +609,8 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { let transfer_result = select( transfer, poll_fn(|cx| { - T::dma_state().rx_err_waker.register(cx.waker()); - match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + self.dma_state.rx_err_waker.register(cx.waker()); + match self.dma_state.rx_errs.swap(0, Ordering::Relaxed) { 0 => Poll::Pending, e => Poll::Ready(Uartris(e as u32)), } @@ -596,7 +623,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { Either::First(()) => { // We're here because the DMA finished, BUT if an error occurred on the LAST // byte, then we may still need to grab the error state! - Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + Uartris(self.dma_state.rx_errs.swap(0, Ordering::Relaxed) as u32) } Either::Second(e) => { // We're here because we errored, which means this is the error that @@ -635,7 +662,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { continue; } - let regs = T::regs(); + let regs = self.info.regs; let all_full = next_addr == eval; // NOTE: This is off label usage of RSR! See the issue below for @@ -685,9 +712,9 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { } } -impl<'d, T: Instance> Uart<'d, T, Blocking> { +impl<'d> Uart<'d, Blocking> { /// Create a new UART without hardware flow control - pub fn new_blocking( + pub fn new_blocking( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -697,7 +724,7 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { } /// Create a new UART with hardware flow control (RTS/CTS) - pub fn new_with_rtscts_blocking( + pub fn new_with_rtscts_blocking( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -720,24 +747,30 @@ impl<'d, T: Instance> Uart<'d, T, Blocking> { /// Convert this uart instance into a buffered uart using the provided /// irq, transmit and receive buffers. - pub fn into_buffered( + pub fn into_buffered( self, - irq: impl Binding>, + _irq: impl Binding>, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], - ) -> BufferedUart<'d, T> { - buffered::init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + ) -> BufferedUart { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); BufferedUart { - rx: BufferedUartRx { phantom: PhantomData }, - tx: BufferedUartTx { phantom: PhantomData }, + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, } } } -impl<'d, T: Instance> Uart<'d, T, Async> { +impl<'d> Uart<'d, Async> { /// Create a new DMA enabled UART without hardware flow control - pub fn new( + pub fn new( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -760,7 +793,7 @@ impl<'d, T: Instance> Uart<'d, T, Async> { } /// Create a new DMA enabled UART with hardware flow control (RTS/CTS) - pub fn new_with_rtscts( + pub fn new_with_rtscts( uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, rx: Peri<'d, impl RxPin>, @@ -785,8 +818,8 @@ impl<'d, T: Instance> Uart<'d, T, Async> { } } -impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { - fn new_inner( +impl<'d, M: Mode> Uart<'d, M> { + fn new_inner( _uart: Peri<'d, T>, mut tx: Peri<'d, AnyPin>, mut rx: Peri<'d, AnyPin>, @@ -798,6 +831,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { config: Config, ) -> Self { Self::init( + T::info(), Some(tx.reborrow()), Some(rx.reborrow()), rts.as_mut().map(|x| x.reborrow()), @@ -806,19 +840,20 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { ); Self { - tx: UartTx::new_inner(tx_dma), - rx: UartRx::new_inner(has_irq, rx_dma), + tx: UartTx::new_inner(T::info(), tx_dma), + rx: UartRx::new_inner(T::info(), T::dma_state(), has_irq, rx_dma), } } fn init( + info: &Info, tx: Option>, rx: Option>, rts: Option>, cts: Option>, config: Config, ) { - let r = T::regs(); + let r = info.regs; if let Some(pin) = &tx { let funcsel = { let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; @@ -896,7 +931,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); } - Self::set_baudrate_inner(config.baudrate); + Self::set_baudrate_inner(info, config.baudrate); let (pen, eps) = match config.parity { Parity::ParityNone => (false, false), @@ -926,8 +961,8 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); } - fn lcr_modify(f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { - let r = T::regs(); + fn lcr_modify(info: &Info, f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { + let r = info.regs; // Notes from PL011 reference manual: // @@ -978,11 +1013,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { /// sets baudrate on runtime pub fn set_baudrate(&mut self, baudrate: u32) { - Self::set_baudrate_inner(baudrate); + Self::set_baudrate_inner(self.tx.info, baudrate); } - fn set_baudrate_inner(baudrate: u32) { - let r = T::regs(); + fn set_baudrate_inner(info: &Info, baudrate: u32) { + let r = info.regs; let clk_base = crate::clocks::clk_peri_freq(); @@ -1002,11 +1037,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); - Self::lcr_modify(|_| {}); + Self::lcr_modify(info, |_| {}); } } -impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { +impl<'d, M: Mode> Uart<'d, M> { /// Transmit the provided buffer blocking execution until done. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buffer) @@ -1034,19 +1069,19 @@ impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { /// Split the Uart into a transmitter and receiver, which is particularly /// useful when having two tasks correlating to transmitting and receiving. - pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) { + pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { (self.tx, self.rx) } /// Split the Uart into a transmitter and receiver by mutable reference, /// which is particularly useful when having two tasks correlating to /// transmitting and receiving. - pub fn split_ref(&mut self) -> (&mut UartTx<'d, T, M>, &mut UartRx<'d, T, M>) { + pub fn split_ref(&mut self) -> (&mut UartTx<'d, M>, &mut UartRx<'d, M>) { (&mut self.tx, &mut self.rx) } } -impl<'d, T: Instance> Uart<'d, T, Async> { +impl<'d> Uart<'d, Async> { /// Write to UART TX from the provided buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.write(buffer).await @@ -1076,10 +1111,10 @@ impl<'d, T: Instance> Uart<'d, T, Async> { } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, M> { type Error = Error; fn read(&mut self) -> Result> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -1100,11 +1135,11 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, M> { type Error = Error; fn write(&mut self, word: u8) -> Result<(), nb::Error> { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().txff() { return Err(nb::Error::WouldBlock); } @@ -1114,7 +1149,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, } fn flush(&mut self) -> Result<(), nb::Error> { - let r = T::regs(); + let r = self.info.regs; if !r.uartfr().read().txfe() { return Err(nb::Error::WouldBlock); } @@ -1122,7 +1157,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, M> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { @@ -1134,7 +1169,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Read for Uart<'d, M> { type Error = Error; fn read(&mut self) -> Result> { @@ -1142,7 +1177,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::serial::Write for Uart<'d, M> { type Error = Error; fn write(&mut self, word: u8) -> Result<(), nb::Error> { @@ -1154,7 +1189,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T } } -impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, M> { type Error = Error; fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { @@ -1177,21 +1212,21 @@ impl embedded_hal_nb::serial::Error for Error { } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, M> { type Error = Error; } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, M> { fn read(&mut self) -> nb::Result { - let r = T::regs(); + let r = self.info.regs; if r.uartfr().read().rxfe() { return Err(nb::Error::WouldBlock); } @@ -1212,7 +1247,7 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) } @@ -1222,11 +1257,11 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, } } -impl<'d, T: Instance> embedded_io::ErrorType for UartTx<'d, T, Blocking> { +impl<'d> embedded_io::ErrorType for UartTx<'d, Blocking> { type Error = Error; } -impl<'d, T: Instance> embedded_io::Write for UartTx<'d, T, Blocking> { +impl<'d> embedded_io::Write for UartTx<'d, Blocking> { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write(buf).map(|_| buf.len()) } @@ -1236,13 +1271,13 @@ impl<'d, T: Instance> embedded_io::Write for UartTx<'d, T, Blocking> { } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, M> { fn read(&mut self) -> Result> { embedded_hal_02::serial::Read::read(&mut self.rx) } } -impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> { +impl<'d, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, M> { fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { self.blocking_write(&[char]).map_err(nb::Error::Other) } @@ -1252,11 +1287,11 @@ impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> } } -impl<'d, T: Instance> embedded_io::ErrorType for Uart<'d, T, Blocking> { +impl<'d> embedded_io::ErrorType for Uart<'d, Blocking> { type Error = Error; } -impl<'d, T: Instance> embedded_io::Write for Uart<'d, T, Blocking> { +impl<'d> embedded_io::Write for Uart<'d, Blocking> { fn write(&mut self, buf: &[u8]) -> Result { self.blocking_write(buf).map(|_| buf.len()) } @@ -1266,13 +1301,17 @@ impl<'d, T: Instance> embedded_io::Write for Uart<'d, T, Blocking> { } } +struct Info { + regs: pac::uart::Uart, + tx_dreq: pac::dma::vals::TreqSel, + rx_dreq: pac::dma::vals::TreqSel, + interrupt: Interrupt, +} + trait SealedMode {} trait SealedInstance { - const TX_DREQ: pac::dma::vals::TreqSel; - const RX_DREQ: pac::dma::vals::TreqSel; - - fn regs() -> pac::uart::Uart; + fn info() -> &'static Info; fn buffered_state() -> &'static buffered::State; @@ -1308,11 +1347,14 @@ pub trait Instance: SealedInstance + PeripheralType { macro_rules! impl_instance { ($inst:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { impl SealedInstance for peripherals::$inst { - const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq; - const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq; - - fn regs() -> pac::uart::Uart { - pac::$inst + fn info() -> &'static Info { + static INFO: Info = Info { + regs: pac::$inst, + tx_dreq: $tx_dreq, + rx_dreq: $rx_dreq, + interrupt: crate::interrupt::typelevel::$irq::IRQ, + }; + &INFO } fn buffered_state() -> &'static buffered::State { diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index c50ab5294..b6781905e 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Modify BufferedUart initialization to take pins before interrupts ([#3983](https://github.com/embassy-rs/embassy/pull/3983)) +- Added a 'single-bank' and a 'dual-bank' feature so chips with configurable flash bank setups are be supported in embassy ([#4125](https://github.com/embassy-rs/embassy/pull/4125)) ## 0.2.0 - 2025-01-10 diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 54badc8f2..972307bec 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -73,7 +73,7 @@ rand_core = "0.6.3" sdio-host = "0.9.0" critical-section = "1.1" #stm32-metapac = { version = "16" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a821bf5dd8d283c1e8de88fc7699235777a07e78" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7251801e3273011ce28a89e8f2e45eec2e419e26" } vcell = "0.1.3" nb = "1.0.0" @@ -102,7 +102,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "16", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-a821bf5dd8d283c1e8de88fc7699235777a07e78", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-7251801e3273011ce28a89e8f2e45eec2e419e26", default-features = false, features = ["metadata"] } [features] default = ["rt"] @@ -197,6 +197,9 @@ split-pc2 = ["_split-pins-enabled"] ## Split PC3 split-pc3 = ["_split-pins-enabled"] +dual-bank = [] +single-bank = [] + ## internal use only _split-pins-enabled = [] diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index f9f03c51b..b00b6a7ac 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -49,6 +49,44 @@ fn main() { } } + // ======== + // Select the memory variant to use + let memory = { + let single_bank_selected = env::var("CARGO_FEATURE_SINGLE_BANK").is_ok(); + let dual_bank_selected = env::var("CARGO_FEATURE_DUAL_BANK").is_ok(); + + let single_bank_memory = METADATA.memory.iter().find(|mem| { + mem.iter().any(|region| region.name.contains("BANK_1")) + && !mem.iter().any(|region| region.name.contains("BANK_2")) + }); + + let dual_bank_memory = METADATA.memory.iter().find(|mem| { + mem.iter().any(|region| region.name.contains("BANK_1")) + && mem.iter().any(|region| region.name.contains("BANK_2")) + }); + + cfgs.set( + "bank_setup_configurable", + single_bank_memory.is_some() && dual_bank_memory.is_some(), + ); + + match (single_bank_selected, dual_bank_selected) { + (true, true) => panic!("Both 'single-bank' and 'dual-bank' features enabled"), + (true, false) => { + single_bank_memory.expect("The 'single-bank' feature is not supported on this dual bank chip") + } + (false, true) => { + dual_bank_memory.expect("The 'dual-bank' feature is not supported on this single bank chip") + } + (false, false) => { + if METADATA.memory.len() != 1 { + panic!("Chip supports single and dual bank configuration. No Cargo feature to select one is enabled. Use the 'single-bank' or 'dual-bank' feature to make your selection") + } + METADATA.memory[0] + } + } + }; + // ======== // Generate singletons @@ -290,8 +328,7 @@ fn main() { // ======== // Generate FLASH regions let mut flash_regions = TokenStream::new(); - let flash_memory_regions: Vec<_> = METADATA - .memory + let flash_memory_regions: Vec<_> = memory .iter() .filter(|x| x.kind == MemoryRegionKind::Flash && x.settings.is_some()) .collect(); @@ -647,6 +684,8 @@ fn main() { PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(p.name, clock), }; + let bus_clock_frequency = clock_gen.gen_clock(p.name, &rcc.bus_clock); + // A refcount leak can result if the same field is shared by peripherals with different stop modes // This condition should be checked in stm32-data let stop_mode = match rcc.stop_mode { @@ -660,6 +699,9 @@ fn main() { fn frequency() -> crate::time::Hertz { #clock_frequency } + fn bus_frequency() -> crate::time::Hertz { + #bus_clock_frequency + } const RCC_INFO: crate::rcc::RccInfo = unsafe { crate::rcc::RccInfo::new( @@ -1616,8 +1658,7 @@ fn main() { let mut pins_table: Vec> = Vec::new(); let mut adc_table: Vec> = Vec::new(); - for m in METADATA - .memory + for m in memory .iter() .filter(|m| m.kind == MemoryRegionKind::Flash && m.settings.is_some()) { @@ -1855,8 +1896,7 @@ fn main() { // ======== // Generate flash constants - let flash_regions: Vec<&MemoryRegion> = METADATA - .memory + let flash_regions: Vec<&MemoryRegion> = memory .iter() .filter(|x| x.kind == MemoryRegionKind::Flash && x.name.starts_with("BANK_")) .collect(); @@ -1981,7 +2021,7 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); if cfg!(feature = "memory-x") { - gen_memory_x(out_dir); + gen_memory_x(memory, out_dir); println!("cargo:rustc-link-search={}", out_dir.display()); } } @@ -2070,11 +2110,11 @@ fn rustfmt(path: impl AsRef) { } } -fn gen_memory_x(out_dir: &Path) { +fn gen_memory_x(memory: &[MemoryRegion], out_dir: &Path) { let mut memory_x = String::new(); - let flash = get_memory_range(MemoryRegionKind::Flash); - let ram = get_memory_range(MemoryRegionKind::Ram); + let flash = get_memory_range(memory, MemoryRegionKind::Flash); + let ram = get_memory_range(memory, MemoryRegionKind::Ram); write!(memory_x, "MEMORY\n{{\n").unwrap(); writeln!( @@ -2098,12 +2138,8 @@ fn gen_memory_x(out_dir: &Path) { std::fs::write(out_dir.join("memory.x"), memory_x.as_bytes()).unwrap(); } -fn get_memory_range(kind: MemoryRegionKind) -> (u32, u32, String) { - let mut mems: Vec<_> = METADATA - .memory - .iter() - .filter(|m| m.kind == kind && m.size != 0) - .collect(); +fn get_memory_range(memory: &[MemoryRegion], kind: MemoryRegionKind) -> (u32, u32, String) { + let mut mems: Vec<_> = memory.iter().filter(|m| m.kind == kind && m.size != 0).collect(); mems.sort_by_key(|m| m.address); let mut start = u32::MAX; diff --git a/embassy-stm32/src/flash/asynch.rs b/embassy-stm32/src/flash/asynch.rs index 599b7bb4e..006dcddeb 100644 --- a/embassy-stm32/src/flash/asynch.rs +++ b/embassy-stm32/src/flash/asynch.rs @@ -6,8 +6,8 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; use super::{ - blocking_read, ensure_sector_aligned, family, get_sector, Async, Error, Flash, FlashLayout, FLASH_BASE, FLASH_SIZE, - WRITE_SIZE, + blocking_read, ensure_sector_aligned, family, get_flash_regions, get_sector, Async, Error, Flash, FlashLayout, + FLASH_BASE, FLASH_SIZE, WRITE_SIZE, }; use crate::interrupt::InterruptExt; use crate::peripherals::FLASH; @@ -34,7 +34,6 @@ impl<'d> Flash<'d, Async> { /// /// See module-level documentation for details on how memory regions work. pub fn into_regions(self) -> FlashLayout<'d, Async> { - assert!(family::is_default_layout()); FlashLayout::new(self.inner) } @@ -123,7 +122,7 @@ pub(super) async unsafe fn write_chunked(base: u32, size: u32, offset: u32, byte pub(super) async unsafe fn erase_sectored(base: u32, from: u32, to: u32) -> Result<(), Error> { let start_address = base + from; let end_address = base + to; - let regions = family::get_flash_regions(); + let regions = get_flash_regions(); ensure_sector_aligned(start_address, end_address, regions)?; diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs index 1376ca4b4..10023e637 100644 --- a/embassy-stm32/src/flash/common.rs +++ b/embassy-stm32/src/flash/common.rs @@ -4,8 +4,8 @@ use core::sync::atomic::{fence, Ordering}; use embassy_hal_internal::drop::OnDrop; use super::{ - family, Async, Blocking, Error, FlashBank, FlashLayout, FlashRegion, FlashSector, FLASH_SIZE, MAX_ERASE_SIZE, - READ_SIZE, WRITE_SIZE, + family, get_flash_regions, Async, Blocking, Error, FlashBank, FlashLayout, FlashRegion, FlashSector, FLASH_SIZE, + MAX_ERASE_SIZE, READ_SIZE, WRITE_SIZE, }; use crate::Peri; use crate::_generated::FLASH_BASE; @@ -20,6 +20,10 @@ pub struct Flash<'d, MODE = Async> { impl<'d> Flash<'d, Blocking> { /// Create a new flash driver, usable in blocking mode. pub fn new_blocking(p: Peri<'d, FLASH>) -> Self { + #[cfg(bank_setup_configurable)] + // Check if the configuration matches the embassy setup + super::check_bank_setup(); + Self { inner: p, _mode: PhantomData, @@ -32,7 +36,6 @@ impl<'d, MODE> Flash<'d, MODE> { /// /// See module-level documentation for details on how memory regions work. pub fn into_blocking_regions(self) -> FlashLayout<'d, Blocking> { - assert!(family::is_default_layout()); FlashLayout::new(self.inner) } @@ -137,7 +140,7 @@ pub(super) unsafe fn blocking_erase( ) -> Result<(), Error> { let start_address = base + from; let end_address = base + to; - let regions = family::get_flash_regions(); + let regions = get_flash_regions(); ensure_sector_aligned(start_address, end_address, regions)?; diff --git a/embassy-stm32/src/flash/f0.rs b/embassy-stm32/src/flash/f0.rs index 402312f68..3f9dbe945 100644 --- a/embassy-stm32/src/flash/f0.rs +++ b/embassy-stm32/src/flash/f0.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.cr().modify(|w| w.set_lock(true)); } diff --git a/embassy-stm32/src/flash/f1f3.rs b/embassy-stm32/src/flash/f1f3.rs index ec237b9ff..bf9ad2893 100644 --- a/embassy-stm32/src/flash/f1f3.rs +++ b/embassy-stm32/src/flash/f1f3.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.cr().modify(|w| w.set_lock(true)); } diff --git a/embassy-stm32/src/flash/f2.rs b/embassy-stm32/src/flash/f2.rs index cdab1fd2d..67e380619 100644 --- a/embassy-stm32/src/flash/f2.rs +++ b/embassy-stm32/src/flash/f2.rs @@ -3,7 +3,7 @@ use core::sync::atomic::{fence, AtomicBool, Ordering}; use pac::flash::regs::Sr; -use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{get_flash_regions, FlashBank, FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; @@ -15,14 +15,6 @@ impl FlashSector { } } -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.cr().modify(|w| w.set_lock(true)); } diff --git a/embassy-stm32/src/flash/f4.rs b/embassy-stm32/src/flash/f4.rs index 687eabaeb..62e0492b5 100644 --- a/embassy-stm32/src/flash/f4.rs +++ b/embassy-stm32/src/flash/f4.rs @@ -4,177 +4,10 @@ use core::sync::atomic::{fence, AtomicBool, Ordering}; use embassy_sync::waitqueue::AtomicWaker; use pac::flash::regs::Sr; -use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{get_flash_regions, FlashBank, FlashSector, WRITE_SIZE}; use crate::_generated::FLASH_SIZE; use crate::flash::Error; use crate::pac; -#[allow(missing_docs)] // TODO -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -mod alt_regions { - use core::marker::PhantomData; - - use crate::Peri; - use crate::_generated::flash_regions::{OTPRegion, BANK1_REGION1, BANK1_REGION2, BANK1_REGION3, OTP_REGION}; - use crate::_generated::FLASH_SIZE; - use crate::flash::{asynch, Async, Bank1Region1, Bank1Region2, Blocking, Error, Flash, FlashBank, FlashRegion}; - use crate::peripherals::FLASH; - - pub const ALT_BANK1_REGION3: FlashRegion = FlashRegion { - size: 3 * BANK1_REGION3.erase_size, - ..BANK1_REGION3 - }; - pub const ALT_BANK2_REGION1: FlashRegion = FlashRegion { - bank: FlashBank::Bank2, - base: BANK1_REGION1.base + FLASH_SIZE as u32 / 2, - ..BANK1_REGION1 - }; - pub const ALT_BANK2_REGION2: FlashRegion = FlashRegion { - bank: FlashBank::Bank2, - base: BANK1_REGION2.base + FLASH_SIZE as u32 / 2, - ..BANK1_REGION2 - }; - pub const ALT_BANK2_REGION3: FlashRegion = FlashRegion { - bank: FlashBank::Bank2, - base: BANK1_REGION3.base + FLASH_SIZE as u32 / 2, - size: 3 * BANK1_REGION3.erase_size, - ..BANK1_REGION3 - }; - - pub const ALT_FLASH_REGIONS: [&FlashRegion; 6] = [ - &BANK1_REGION1, - &BANK1_REGION2, - &ALT_BANK1_REGION3, - &ALT_BANK2_REGION1, - &ALT_BANK2_REGION2, - &ALT_BANK2_REGION3, - ]; - - pub struct AltBank1Region3<'d, MODE = Async>(pub &'static FlashRegion, Peri<'d, FLASH>, PhantomData); - pub struct AltBank2Region1<'d, MODE = Async>(pub &'static FlashRegion, Peri<'d, FLASH>, PhantomData); - pub struct AltBank2Region2<'d, MODE = Async>(pub &'static FlashRegion, Peri<'d, FLASH>, PhantomData); - pub struct AltBank2Region3<'d, MODE = Async>(pub &'static FlashRegion, Peri<'d, FLASH>, PhantomData); - - pub struct AltFlashLayout<'d, MODE = Async> { - pub bank1_region1: Bank1Region1<'d, MODE>, - pub bank1_region2: Bank1Region2<'d, MODE>, - pub bank1_region3: AltBank1Region3<'d, MODE>, - pub bank2_region1: AltBank2Region1<'d, MODE>, - pub bank2_region2: AltBank2Region2<'d, MODE>, - pub bank2_region3: AltBank2Region3<'d, MODE>, - pub otp_region: OTPRegion<'d, MODE>, - } - - impl<'d> Flash<'d> { - pub fn into_alt_regions(self) -> AltFlashLayout<'d, Async> { - assert!(!super::is_default_layout()); - - // SAFETY: We never expose the cloned peripheral references, and their instance is not public. - // Also, all async flash region operations are protected with a mutex. - let p = self.inner; - AltFlashLayout { - bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - otp_region: OTPRegion(&OTP_REGION, unsafe { p.clone_unchecked() }, PhantomData), - } - } - - pub fn into_alt_blocking_regions(self) -> AltFlashLayout<'d, Blocking> { - assert!(!super::is_default_layout()); - - // SAFETY: We never expose the cloned peripheral references, and their instance is not public. - // Also, all blocking flash region operations are protected with a cs. - let p = self.inner; - AltFlashLayout { - bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), - bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), - otp_region: OTPRegion(&OTP_REGION, unsafe { p.clone_unchecked() }, PhantomData), - } - } - } - - macro_rules! foreach_altflash_region { - ($type_name:ident, $region:ident) => { - impl $type_name<'_, MODE> { - pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { - crate::flash::common::blocking_read(self.0.base, self.0.size, offset, bytes) - } - } - - impl $type_name<'_, Async> { - pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { - self.blocking_read(offset, bytes) - } - - pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { - let _guard = asynch::REGION_ACCESS.lock().await; - unsafe { asynch::write_chunked(self.0.base, self.0.size, offset, bytes).await } - } - - pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { - let _guard = asynch::REGION_ACCESS.lock().await; - unsafe { asynch::erase_sectored(self.0.base, from, to).await } - } - } - - impl embedded_storage::nor_flash::ErrorType for $type_name<'_, MODE> { - type Error = Error; - } - - impl embedded_storage::nor_flash::ReadNorFlash for $type_name<'_, MODE> { - const READ_SIZE: usize = crate::flash::READ_SIZE; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(offset, bytes) - } - - fn capacity(&self) -> usize { - self.0.size as usize - } - } - - impl embedded_storage_async::nor_flash::ReadNorFlash for $type_name<'_, Async> { - const READ_SIZE: usize = crate::flash::READ_SIZE; - - async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.read(offset, bytes).await - } - - fn capacity(&self) -> usize { - self.0.size as usize - } - } - - impl embedded_storage_async::nor_flash::NorFlash for $type_name<'_, Async> { - const WRITE_SIZE: usize = $region.write_size as usize; - const ERASE_SIZE: usize = $region.erase_size as usize; - - async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.write(offset, bytes).await - } - - async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.erase(from, to).await - } - } - }; - } - - foreach_altflash_region!(AltBank1Region3, ALT_BANK1_REGION3); - foreach_altflash_region!(AltBank2Region1, ALT_BANK2_REGION1); - foreach_altflash_region!(AltBank2Region2, ALT_BANK2_REGION2); - foreach_altflash_region!(AltBank2Region3, ALT_BANK2_REGION3); -} - -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -pub use alt_regions::*; static WAKER: AtomicWaker = AtomicWaker::new(); static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); @@ -185,30 +18,6 @@ impl FlashSector { } } -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -pub(crate) fn is_default_layout() -> bool { - !pac::FLASH.optcr().read().db1m() -} - -#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] -pub(crate) const fn is_default_layout() -> bool { - true -} - -#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] -pub fn get_flash_regions() -> &'static [&'static FlashRegion] { - if is_default_layout() { - &FLASH_REGIONS - } else { - &ALT_FLASH_REGIONS - } -} - -#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] -pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn on_interrupt() { // Clear IRQ flags pac::FLASH.sr().write(|w| { @@ -487,71 +296,83 @@ mod tests { const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; const LARGE_SECTOR_SIZE: u32 = 128 * 1024; - let assert_sector = |snb: u8, index_in_bank: u8, start: u32, size: u32, address: u32| { - let sector = get_sector(address, &FLASH_REGIONS); - assert_eq!(snb, sector.snb()); - assert_eq!( - FlashSector { - bank: FlashBank::Bank1, - index_in_bank, - start, - size - }, - sector - ); - }; + if !cfg!(feature = "dual-bank") { + let assert_sector = |snb: u8, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, crate::flash::get_flash_regions()); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank: sector.bank, + index_in_bank, + start, + size + }, + sector + ); + }; - assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); - assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); - assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); - assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); - assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); - assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); - assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); - assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); - assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); - assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } else { + let assert_sector = |snb: u8, bank: FlashBank, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, crate::flash::get_flash_regions()); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank, + index_in_bank, + start, + size + }, + sector + ) + }; - let assert_sector = |snb: u8, bank: FlashBank, index_in_bank: u8, start: u32, size: u32, address: u32| { - let sector = get_sector(address, &ALT_FLASH_REGIONS); - assert_eq!(snb, sector.snb()); - assert_eq!( - FlashSector { - bank, - index_in_bank, - start, - size - }, - sector - ) - }; + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); - assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); - assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); - assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); - assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); - assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); - assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); - assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); - assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); - assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); - assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_0000); + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_3FFF); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_C000); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_FFFF); - assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_0000); - assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_3FFF); - assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_C000); - assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_FFFF); + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_0000); + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_FFFF); - assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_0000); - assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_FFFF); - - assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080A_0000); - assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080B_FFFF); - assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); - assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080A_0000); + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080B_FFFF); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } + } +} + +#[cfg(all(bank_setup_configurable))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optcr().read().db1m() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the db1m value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optcr().read().db1m() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the db1m value in the user option bytes or configure embassy to use single-bank config"); } } diff --git a/embassy-stm32/src/flash/f7.rs b/embassy-stm32/src/flash/f7.rs index 09ebe9db9..0547c747a 100644 --- a/embassy-stm32/src/flash/f7.rs +++ b/embassy-stm32/src/flash/f7.rs @@ -1,16 +1,14 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } } pub(crate) unsafe fn lock() { @@ -53,7 +51,7 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { pac::FLASH.cr().modify(|w| { w.set_ser(true); - w.set_snb(sector.index_in_bank) + w.set_snb(sector.snb()) }); pac::FLASH.cr().modify(|w| { @@ -118,7 +116,7 @@ mod tests { start, size }, - get_sector(address, &FLASH_REGIONS) + get_sector(address, crate::flash::get_flash_regions()) ) }; @@ -137,7 +135,7 @@ mod tests { } #[test] - #[cfg(stm32f769)] + #[cfg(all(stm32f769, feature = "single-bank"))] fn can_get_sector() { const SMALL_SECTOR_SIZE: u32 = 32 * 1024; const MEDIUM_SECTOR_SIZE: u32 = 128 * 1024; @@ -151,7 +149,7 @@ mod tests { start, size }, - get_sector(address, &FLASH_REGIONS) + get_sector(address, crate::flash::get_flash_regions()) ) }; @@ -168,4 +166,61 @@ mod tests { assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080C_0000); assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); } + + #[test] + #[cfg(all(stm32f769, feature = "dual-bank"))] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 16 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; + const LARGE_SECTOR_SIZE: u32 = 128 * 1024; + + let assert_sector = |index_in_bank: u8, start: u32, size: u32, address: u32, snb: u8, bank: FlashBank| { + assert_eq!( + FlashSector { + bank: bank, + index_in_bank, + start, + size + }, + get_sector(address, crate::flash::get_flash_regions()) + ); + assert_eq!(get_sector(address, crate::flash::get_flash_regions()).snb(), snb); + }; + + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000, 0x00, FlashBank::Bank1); + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF, 0x00, FlashBank::Bank1); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000, 0x03, FlashBank::Bank1); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF, 0x03, FlashBank::Bank1); + + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000, 0x04, FlashBank::Bank1); + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF, 0x04, FlashBank::Bank1); + + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000, 0x05, FlashBank::Bank1); + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF, 0x05, FlashBank::Bank1); + assert_sector(10, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080C_0000, 0x0A, FlashBank::Bank1); + assert_sector(10, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080D_FFFF, 0x0A, FlashBank::Bank1); + + assert_sector(0, 0x0810_0000, SMALL_SECTOR_SIZE, 0x0810_0000, 0x10, FlashBank::Bank2); + assert_sector(0, 0x0810_0000, SMALL_SECTOR_SIZE, 0x0810_3FFF, 0x10, FlashBank::Bank2); + assert_sector(3, 0x0810_C000, SMALL_SECTOR_SIZE, 0x0810_C000, 0x13, FlashBank::Bank2); + assert_sector(3, 0x0810_C000, SMALL_SECTOR_SIZE, 0x0810_FFFF, 0x13, FlashBank::Bank2); + + assert_sector(4, 0x0811_0000, MEDIUM_SECTOR_SIZE, 0x0811_0000, 0x14, FlashBank::Bank2); + assert_sector(4, 0x0811_0000, MEDIUM_SECTOR_SIZE, 0x0811_FFFF, 0x14, FlashBank::Bank2); + + assert_sector(5, 0x0812_0000, LARGE_SECTOR_SIZE, 0x0812_0000, 0x15, FlashBank::Bank2); + assert_sector(5, 0x0812_0000, LARGE_SECTOR_SIZE, 0x0813_FFFF, 0x15, FlashBank::Bank2); + assert_sector(10, 0x081C_0000, LARGE_SECTOR_SIZE, 0x081C_0000, 0x1A, FlashBank::Bank2); + assert_sector(10, 0x081C_0000, LARGE_SECTOR_SIZE, 0x081D_FFFF, 0x1A, FlashBank::Bank2); + } +} + +#[cfg(all(bank_setup_configurable))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && !pac::FLASH.optcr().read().n_dbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the ndbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && pac::FLASH.optcr().read().n_dbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the ndbank value in the user option bytes or configure embassy to use single-bank config"); + } } diff --git a/embassy-stm32/src/flash/g.rs b/embassy-stm32/src/flash/g.rs index 83663743c..bc1fd360c 100644 --- a/embassy-stm32/src/flash/g.rs +++ b/embassy-stm32/src/flash/g.rs @@ -3,18 +3,10 @@ use core::sync::atomic::{fence, Ordering}; use cortex_m::interrupt; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.cr().modify(|w| w.set_lock(true)); } @@ -109,3 +101,23 @@ fn wait_busy() { fn wait_busy() { while pac::FLASH.sr().read().bsy() {} } + +#[cfg(all(bank_setup_configurable, any(flash_g4c2, flash_g4c3, flash_g4c4)))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use single-bank config"); + } +} + +#[cfg(all(bank_setup_configurable, flash_g0x1))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dual_bank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dual_bank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dual_bank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dual_bank value in the user option bytes or configure embassy to use single-bank config"); + } +} diff --git a/embassy-stm32/src/flash/h5.rs b/embassy-stm32/src/flash/h5.rs index d95de2e38..fd9bfcc75 100644 --- a/embassy-stm32/src/flash/h5.rs +++ b/embassy-stm32/src/flash/h5.rs @@ -1,22 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -// const fn is_dual_bank() -> bool { -// FLASH_REGIONS.len() >= 2 -// } - -pub(crate) fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { if !pac::FLASH.nscr().read().lock() { pac::FLASH.nscr().modify(|r| { diff --git a/embassy-stm32/src/flash/h50.rs b/embassy-stm32/src/flash/h50.rs index 74cd6cc03..f8e210556 100644 --- a/embassy-stm32/src/flash/h50.rs +++ b/embassy-stm32/src/flash/h50.rs @@ -8,17 +8,9 @@ use cortex_m::interrupt; use pac::flash::regs::Nssr; use pac::flash::vals::Bksel; -use super::{Error, FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{Error, FlashBank, FlashSector, WRITE_SIZE}; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.nscr().modify(|w| w.set_lock(true)); } diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs index 254915381..f1d84101c 100644 --- a/embassy-stm32/src/flash/h7.rs +++ b/embassy-stm32/src/flash/h7.rs @@ -1,22 +1,14 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, BANK1_REGION, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, BANK1_REGION, FLASH_REGIONS, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - const fn is_dual_bank() -> bool { FLASH_REGIONS.len() >= 2 } -pub(crate) fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.bank(0).cr().modify(|w| w.set_lock(true)); if is_dual_bank() { diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs index ea00bf499..65cea005c 100644 --- a/embassy-stm32/src/flash/l.rs +++ b/embassy-stm32/src/flash/l.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().modify(|w| w.set_lock(true)); @@ -238,3 +230,23 @@ unsafe fn wait_ready_blocking() -> Result<(), Error> { } } } + +#[cfg(all(bank_setup_configurable, flash_l5))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dbank value in the user option bytes or configure embassy to use single-bank config"); + } +} + +#[cfg(all(bank_setup_configurable, flash_l4))] +pub(crate) fn check_bank_setup() { + if cfg!(feature = "single-bank") && pac::FLASH.optr().read().dualbank() { + panic!("Embassy is configured as single-bank, but the hardware is running in dual-bank mode. Change the hardware by changing the dualbank value in the user option bytes or configure embassy to use dual-bank config"); + } + if cfg!(feature = "dual-bank") && !pac::FLASH.optr().read().dualbank() { + panic!("Embassy is configured as dual-bank, but the hardware is running in single-bank mode. Change the hardware by changing the dualbank value in the user option bytes or configure embassy to use single-bank config"); + } +} diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index c7488c8ef..adc45db9c 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -14,17 +14,9 @@ pub use common::*; pub use crate::_generated::flash_regions::*; pub use crate::_generated::{FLASH_BASE, FLASH_SIZE, MAX_ERASE_SIZE, WRITE_SIZE}; -/// Get whether the default flash layout is being used. -/// -/// In some chips, dual-bank is not default. This will then return `false` -/// when dual-bank is enabled. -pub fn is_default_layout() -> bool { - family::is_default_layout() -} - /// Get all flash regions. pub fn get_flash_regions() -> &'static [&'static FlashRegion] { - family::get_flash_regions() + &FLASH_REGIONS } /// Read size (always 1) diff --git a/embassy-stm32/src/flash/other.rs b/embassy-stm32/src/flash/other.rs index 20f84a72f..293a79be3 100644 --- a/embassy-stm32/src/flash/other.rs +++ b/embassy-stm32/src/flash/other.rs @@ -1,14 +1,6 @@ #![allow(unused)] -use super::{Error, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; - -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} +use super::{Error, FlashSector, WRITE_SIZE}; pub(crate) unsafe fn lock() { unimplemented!(); diff --git a/embassy-stm32/src/flash/u0.rs b/embassy-stm32/src/flash/u0.rs index bfdbd15a5..68d847eca 100644 --- a/embassy-stm32/src/flash/u0.rs +++ b/embassy-stm32/src/flash/u0.rs @@ -3,18 +3,10 @@ use core::sync::atomic::{fence, Ordering}; use cortex_m::interrupt; -use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { pac::FLASH.cr().modify(|w| w.set_lock(true)); } diff --git a/embassy-stm32/src/flash/u5.rs b/embassy-stm32/src/flash/u5.rs index dad698316..131caa195 100644 --- a/embassy-stm32/src/flash/u5.rs +++ b/embassy-stm32/src/flash/u5.rs @@ -1,18 +1,10 @@ use core::ptr::write_volatile; use core::sync::atomic::{fence, Ordering}; -use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use super::{FlashBank, FlashSector, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -pub(crate) const fn is_default_layout() -> bool { - true -} - -pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { - &FLASH_REGIONS -} - pub(crate) unsafe fn lock() { #[cfg(feature = "trustzone-secure")] pac::FLASH.seccr().modify(|w| w.set_lock(true)); diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 226293a9d..f8d09413d 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -173,8 +173,9 @@ pub use crate::_generated::interrupt; // developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. #[macro_export] macro_rules! bind_interrupts { - ($vis:vis struct $name:ident { + ($(#[$outer:meta])* $vis:vis struct $name:ident { $( + $(#[$inner:meta])* $(#[cfg($cond_irq:meta)])? $irq:ident => $( $(#[cfg($cond_handler:meta)])? @@ -183,12 +184,14 @@ macro_rules! bind_interrupts { )* }) => { #[derive(Copy, Clone)] + $(#[$outer])* $vis struct $name; $( #[allow(non_snake_case)] #[no_mangle] $(#[cfg($cond_irq)])? + $(#[$inner])* unsafe extern "C" fn $irq() { $( $(#[cfg($cond_handler)])? @@ -600,17 +603,7 @@ fn init_hw(config: Config) -> Peripherals { #[cfg(feature = "exti")] exti::init(cs); - rcc::init(config.rcc); - - // must be after rcc init - #[cfg(feature = "_time-driver")] - time_driver::init(cs); - - #[cfg(feature = "low-power")] - { - crate::rcc::REFCOUNT_STOP2 = 0; - crate::rcc::REFCOUNT_STOP1 = 0; - } + rcc::init_rcc(cs, config.rcc); } p diff --git a/embassy-stm32/src/opamp.rs b/embassy-stm32/src/opamp.rs index b368df6c3..2eb2e61c1 100644 --- a/embassy-stm32/src/opamp.rs +++ b/embassy-stm32/src/opamp.rs @@ -239,7 +239,7 @@ impl<'d, T: Instance> OpAmp<'d, T> { T::regs().csr().modify(|w| { w.set_vp_sel(VpSel::from_bits(pin.channel())); - w.set_vm_sel(VmSel::OUTPUT); + w.set_vm_sel(VmSel::PGA); w.set_pga_gain(pga_gain); w.set_opaintoen(true); w.set_opampen(true); diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 4f43d3748..3733fed56 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -97,6 +97,8 @@ pub(crate) unsafe fn get_freqs() -> &'static Clocks { pub(crate) trait SealedRccPeripheral { fn frequency() -> Hertz; + #[allow(dead_code)] + fn bus_frequency() -> Hertz; const RCC_INFO: RccInfo; } @@ -369,3 +371,32 @@ pub fn enable_and_reset() { pub fn disable() { T::RCC_INFO.disable(); } + +/// Re-initialize the `embassy-stm32` clock configuration with the provided configuration. +/// +/// This is useful when you need to alter the CPU clock after configuring peripherals. +/// For instance, configure an external clock via spi or i2c. +/// +/// Please not this only re-configures the rcc and the time driver (not GPIO, EXTI, etc). +/// +/// This should only be called after `init`. +#[cfg(not(feature = "_dual-core"))] +pub fn reinit(config: Config) { + critical_section::with(|cs| init_rcc(cs, config)) +} + +pub(crate) fn init_rcc(_cs: CriticalSection, config: Config) { + unsafe { + init(config); + + // must be after rcc init + #[cfg(feature = "_time-driver")] + crate::time_driver::init(_cs); + + #[cfg(feature = "low-power")] + { + REFCOUNT_STOP2 = 0; + REFCOUNT_STOP1 = 0; + } + } +} diff --git a/embassy-stm32/src/spdifrx/mod.rs b/embassy-stm32/src/spdifrx/mod.rs index 08dba04fe..9c42217f0 100644 --- a/embassy-stm32/src/spdifrx/mod.rs +++ b/embassy-stm32/src/spdifrx/mod.rs @@ -223,7 +223,7 @@ impl<'d, T: Instance> Spdifrx<'d, T> { }; for sample in data.as_mut() { - if (*sample & (0x0002_u32)) == 0x0001 { + if (*sample & (0x0002_u32)) != 0 { // Discard invalid samples, setting them to mute level. *sample = 0; } else { diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 54ab7d0d5..8fd7e8df4 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -31,6 +31,8 @@ pub struct PwmPin<'d, T, C> { /// PWM pin config /// /// This configures the pwm pin settings +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct PwmPinConfig { /// PWM Pin output type pub output_type: OutputType, diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index 2aca3d929..590d1a427 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -549,7 +549,7 @@ foreach_interrupt!( ); fn calculate_trdt(speed: Dspd) -> u8 { - let ahb_freq = T::frequency().0; + let ahb_freq = T::bus_frequency().0; match speed { Dspd::HIGH_SPEED => { // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index a0f4b5a74..e7095401e 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -6,7 +6,7 @@ use core::task::{Context, Poll, Waker}; use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; -/// Single-slot signaling primitive. +/// Single-slot signaling primitive for a _single_ consumer. /// /// This is similar to a [`Channel`](crate::channel::Channel) with a buffer size of 1, except /// "sending" to it (calling [`Signal::signal`]) when full will overwrite the previous value instead @@ -17,6 +17,7 @@ use crate::blocking_mutex::Mutex; /// updates. /// /// For more advanced use cases, you might want to use [`Channel`](crate::channel::Channel) instead. +/// For multiple consumers, use [`Watch`](crate::watch::Watch) instead. /// /// Signals are generally declared as `static`s and then borrowed as required. /// @@ -106,7 +107,7 @@ where }) } - /// Future that completes when this Signal has been signaled. + /// Future that completes when this Signal has been signaled, taking the value out of the signal. pub fn wait(&self) -> impl Future + '_ { poll_fn(move |cx| self.poll_wait(cx)) } diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs index e76646c0b..08d6a833d 100644 --- a/embassy-sync/src/watch.rs +++ b/embassy-sync/src/watch.rs @@ -10,7 +10,7 @@ use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex; use crate::waitqueue::MultiWakerRegistration; -/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// The `Watch` is a single-slot signaling primitive that allows _multiple_ (`N`) receivers to concurrently await /// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, /// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous /// value when a new one is sent, without waiting for all receivers to read the previous value. @@ -298,7 +298,7 @@ impl WatchBehavior for Watch } impl Watch { - /// Create a new `Watch` channel. + /// Create a new `Watch` channel for `N` receivers. pub const fn new() -> Self { Self { mutex: Mutex::new(RefCell::new(WatchState { diff --git a/embassy-time-driver/Cargo.toml b/embassy-time-driver/Cargo.toml index b709cafc1..16213cb75 100644 --- a/embassy-time-driver/Cargo.toml +++ b/embassy-time-driver/Cargo.toml @@ -226,6 +226,8 @@ tick-hz-128_000_000 = [] tick-hz-130_000_000 = [] ## 131.072MHz Tick Rate tick-hz-131_072_000 = [] +## 133.0MHz Tick Rate +tick-hz-133_000_000 = [] ## 140.0MHz Tick Rate tick-hz-140_000_000 = [] ## 144.0MHz Tick Rate diff --git a/embassy-time-driver/gen_tick.py b/embassy-time-driver/gen_tick.py index af194c31f..080434457 100644 --- a/embassy-time-driver/gen_tick.py +++ b/embassy-time-driver/gen_tick.py @@ -22,6 +22,7 @@ for i in range(1, 30): ticks.append(10 * i * 1_000_000) for i in range(15, 50): ticks.append(20 * i * 1_000_000) +ticks.append(133 * 1_000_000) seen = set() ticks = sorted([x for x in ticks if not (x in seen or seen.add(x))]) diff --git a/embassy-time-driver/src/tick.rs b/embassy-time-driver/src/tick.rs index 916ae9498..5059e1628 100644 --- a/embassy-time-driver/src/tick.rs +++ b/embassy-time-driver/src/tick.rs @@ -182,6 +182,8 @@ pub const TICK_HZ: u64 = 128_000_000; pub const TICK_HZ: u64 = 130_000_000; #[cfg(feature = "tick-hz-131_072_000")] pub const TICK_HZ: u64 = 131_072_000; +#[cfg(feature = "tick-hz-133_000_000")] +pub const TICK_HZ: u64 = 133_000_000; #[cfg(feature = "tick-hz-140_000_000")] pub const TICK_HZ: u64 = 140_000_000; #[cfg(feature = "tick-hz-144_000_000")] @@ -410,6 +412,7 @@ pub const TICK_HZ: u64 = 5_242_880_000; feature = "tick-hz-128_000_000", feature = "tick-hz-130_000_000", feature = "tick-hz-131_072_000", + feature = "tick-hz-133_000_000", feature = "tick-hz-140_000_000", feature = "tick-hz-144_000_000", feature = "tick-hz-150_000_000", diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index 80a39dbf5..dc144ec3c 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -274,6 +274,8 @@ tick-hz-128_000_000 = ["embassy-time-driver/tick-hz-128_000_000"] tick-hz-130_000_000 = ["embassy-time-driver/tick-hz-130_000_000"] ## 131.072MHz Tick Rate tick-hz-131_072_000 = ["embassy-time-driver/tick-hz-131_072_000"] +## 133.0MHz Tick Rate +tick-hz-133_000_000 = ["embassy-time-driver/tick-hz-133_000_000"] ## 140.0MHz Tick Rate tick-hz-140_000_000 = ["embassy-time-driver/tick-hz-140_000_000"] ## 144.0MHz Tick Rate diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs index f0d7626f6..6ad07a78c 100644 --- a/embassy-usb-dfu/src/application.rs +++ b/embassy-usb-dfu/src/application.rs @@ -1,5 +1,3 @@ -use core::marker::PhantomData; - use embassy_boot::BlockingFirmwareState; use embassy_time::{Duration, Instant}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; @@ -13,31 +11,47 @@ use crate::consts::{ }; use crate::Reset; +/// Generic interface for a system that can signal to the bootloader that USB DFU mode is needed on the next boot. +/// +/// By default this trait is implemented for `BlockingFirmwareState<'d, STATE>` but you could also implement this generic +/// interface yourself instead in more complex situations. This could for instance be when you cannot hand ownership of a +/// `BlockingFirmwareState` instance over directly to the DFU `Control` instance and need to use a more complex mechanism. +pub trait DfuMarker { + /// Signal to the bootloader that DFU mode should be used on the next boot. + fn mark_dfu(&mut self); +} + +impl<'d, STATE: NorFlash> DfuMarker for BlockingFirmwareState<'d, STATE> { + fn mark_dfu(&mut self) { + self.mark_dfu().expect("Failed to mark DFU mode in bootloader") + } +} + /// Internal state for the DFU class -pub struct Control<'d, STATE: NorFlash, RST: Reset> { - firmware_state: BlockingFirmwareState<'d, STATE>, +pub struct Control { + dfu_marker: MARK, attrs: DfuAttributes, state: State, timeout: Option, detach_start: Option, - _rst: PhantomData, + reset: RST, } -impl<'d, STATE: NorFlash, RST: Reset> Control<'d, STATE, RST> { +impl Control { /// Create a new DFU instance to expose a DFU interface. - pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self { + pub fn new(dfu_marker: MARK, attrs: DfuAttributes, reset: RST) -> Self { Control { - firmware_state, + dfu_marker, attrs, state: State::AppIdle, detach_start: None, timeout: None, - _rst: PhantomData, + reset, } } } -impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { +impl Handler for Control { fn reset(&mut self) { if let Some(start) = self.detach_start { let delta = Instant::now() - start; @@ -48,10 +62,8 @@ impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { timeout.as_millis() ); if delta < timeout { - self.firmware_state - .mark_dfu() - .expect("Failed to mark DFU mode in bootloader"); - RST::sys_reset() + self.dfu_marker.mark_dfu(); + self.reset.sys_reset() } } } @@ -69,10 +81,15 @@ impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { match Request::try_from(req.request) { Ok(Request::Detach) => { - trace!("Received DETACH, awaiting USB reset"); self.detach_start = Some(Instant::now()); self.timeout = Some(Duration::from_millis(req.value as u64)); self.state = State::AppDetach; + if self.attrs.contains(DfuAttributes::WILL_DETACH) { + trace!("Received DETACH, performing reset"); + self.reset(); + } else { + trace!("Received DETACH, awaiting USB reset"); + } Some(OutResponse::Accepted) } _ => None, @@ -109,9 +126,9 @@ impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { /// it should expose a DFU device, and a software reset will be issued. /// /// To apply USB DFU updates, the bootloader must be capable of recognizing the DFU magic and exposing a device to handle the full DFU transaction with the host. -pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash, RST: Reset>( +pub fn usb_dfu<'d, D: Driver<'d>, MARK: DfuMarker, RST: Reset>( builder: &mut Builder<'d, D>, - handler: &'d mut Control<'d, STATE, RST>, + handler: &'d mut Control, timeout: Duration, ) { let mut func = builder.function(0x00, 0x00, 0x00); diff --git a/embassy-usb-dfu/src/dfu.rs b/embassy-usb-dfu/src/dfu.rs index c23286cf5..a98d6ab40 100644 --- a/embassy-usb-dfu/src/dfu.rs +++ b/embassy-usb-dfu/src/dfu.rs @@ -1,5 +1,3 @@ -use core::marker::PhantomData; - use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterError}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; use embassy_usb::driver::Driver; @@ -20,12 +18,12 @@ pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_S status: Status, offset: usize, buf: AlignedBuffer, - _rst: PhantomData, + reset: RST, } impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> { /// Create a new DFU instance to handle DFU transfers. - pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { + pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes, reset: RST) -> Self { Self { updater, attrs, @@ -33,7 +31,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Co status: Status::Ok, offset: 0, buf: AlignedBuffer([0; BLOCK_SIZE]), - _rst: PhantomData, + reset, } } @@ -155,14 +153,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha } match Request::try_from(req.request) { Ok(Request::GetStatus) => { - //TODO: Configurable poll timeout, ability to add string for Vendor error - buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); match self.state { State::DlSync => self.state = State::Download, - State::ManifestSync => RST::sys_reset(), + State::ManifestSync => self.reset.sys_reset(), _ => {} } + //TODO: Configurable poll timeout, ability to add string for Vendor error + buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); Some(InResponse::Accepted(&buf[0..6])) } Ok(Request::GetState) => { diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs index eaa4b6e33..54ffa7276 100644 --- a/embassy-usb-dfu/src/lib.rs +++ b/embassy-usb-dfu/src/lib.rs @@ -26,10 +26,10 @@ compile_error!("usb-dfu must be compiled with exactly one of `dfu`, or `applicat /// This crate exposes `ResetImmediate` when compiled with cortex-m or esp32c3 support, which immediately issues a /// reset request without interfacing with any other peripherals. /// -/// If alternate behaviour is desired, a custom implementation of Reset can be provided as a type argument to the usb_dfu function. +/// If alternate behaviour is desired, a custom implementation of Reset can be provided as an argument to the usb_dfu function. pub trait Reset { /// Reset the device. - fn sys_reset() -> !; + fn sys_reset(&self); } /// Reset immediately. @@ -38,7 +38,7 @@ pub struct ResetImmediate; #[cfg(feature = "esp32c3-hal")] impl Reset for ResetImmediate { - fn sys_reset() -> ! { + fn sys_reset(&self) { esp32c3_hal::reset::software_reset(); loop {} } @@ -50,7 +50,7 @@ pub struct ResetImmediate; #[cfg(feature = "cortex-m")] impl Reset for ResetImmediate { - fn sys_reset() -> ! { + fn sys_reset(&self) { cortex_m::peripheral::SCB::sys_reset() } } diff --git a/embassy-usb-driver/Cargo.toml b/embassy-usb-driver/Cargo.toml index 41493f00d..edb6551b0 100644 --- a/embassy-usb-driver/Cargo.toml +++ b/embassy-usb-driver/Cargo.toml @@ -20,3 +20,4 @@ features = ["defmt"] [dependencies] defmt = { version = "0.3", optional = true } +embedded-io-async = "0.6.1" diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs index 3b705c8c4..d204e4d85 100644 --- a/embassy-usb-driver/src/lib.rs +++ b/embassy-usb-driver/src/lib.rs @@ -395,3 +395,12 @@ pub enum EndpointError { /// The endpoint is disabled. Disabled, } + +impl embedded_io_async::Error for EndpointError { + fn kind(&self) -> embedded_io_async::ErrorKind { + match self { + Self::BufferOverflow => embedded_io_async::ErrorKind::OutOfMemory, + Self::Disabled => embedded_io_async::ErrorKind::NotConnected, + } + } +} diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 771190c89..4950fbe2a 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -52,6 +52,7 @@ embassy-sync = { version = "0.6.2", path = "../embassy-sync" } embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } defmt = { version = "0.3", optional = true } +embedded-io-async = "0.6.1" log = { version = "0.4.14", optional = true } heapless = "0.8" diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs index ea9d9fb7b..732a433f8 100644 --- a/embassy-usb/src/class/cdc_acm.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -410,6 +410,18 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { } } +impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for Sender<'d, D> { + type Error = EndpointError; +} + +impl<'d, D: Driver<'d>> embedded_io_async::Write for Sender<'d, D> { + async fn write(&mut self, buf: &[u8]) -> Result { + let len = core::cmp::min(buf.len(), self.max_packet_size() as usize); + self.write_packet(&buf[..len]).await?; + Ok(len) + } +} + /// CDC ACM class packet receiver. /// /// You can obtain a `Receiver` with [`CdcAcmClass::split`] @@ -451,6 +463,93 @@ impl<'d, D: Driver<'d>> Receiver<'d, D> { pub async fn wait_connection(&mut self) { self.read_ep.wait_enabled().await; } + + /// Turn the `Receiver` into a [`BufferedReceiver`]. + /// + /// The supplied buffer must be large enough to hold max_packet_size bytes. + pub fn into_buffered(self, buf: &'d mut [u8]) -> BufferedReceiver<'d, D> { + BufferedReceiver { + receiver: self, + buffer: buf, + start: 0, + end: 0, + } + } +} + +/// CDC ACM class buffered receiver. +/// +/// It is a requirement of the [`embedded_io_async::Read`] trait that arbitrarily small lengths of +/// data can be read from the stream. The [`Receiver`] can only read full packets at a time. The +/// `BufferedReceiver` instead buffers a single packet if the caller does not read all of the data, +/// so that the remaining data can be returned in subsequent calls. +/// +/// If you have no requirement to use the [`embedded_io_async::Read`] trait or to read a data length +/// less than the packet length, then it is more efficient to use the [`Receiver`] directly. +/// +/// You can obtain a `BufferedReceiver` with [`Receiver::into_buffered`]. +/// +/// [`embedded_io_async::Read`]: https://docs.rs/embedded-io-async/latest/embedded_io_async/trait.Read.html +pub struct BufferedReceiver<'d, D: Driver<'d>> { + receiver: Receiver<'d, D>, + buffer: &'d mut [u8], + start: usize, + end: usize, +} + +impl<'d, D: Driver<'d>> BufferedReceiver<'d, D> { + fn read_from_buffer(&mut self, buf: &mut [u8]) -> usize { + let available = &self.buffer[self.start..self.end]; + let len = core::cmp::min(available.len(), buf.len()); + buf[..len].copy_from_slice(&self.buffer[..len]); + self.start += len; + len + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.receiver.line_coding() + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.receiver.dtr() + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.receiver.rts() + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.receiver.wait_connection().await; + } +} + +impl<'d, D: Driver<'d>> embedded_io_async::ErrorType for BufferedReceiver<'d, D> { + type Error = EndpointError; +} + +impl<'d, D: Driver<'d>> embedded_io_async::Read for BufferedReceiver<'d, D> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + // If there is a buffered packet, return data from that first + if self.start != self.end { + return Ok(self.read_from_buffer(buf)); + } + + // If the caller's buffer is large enough to contain an entire packet, read directly into + // that instead of buffering the packet internally. + if buf.len() > self.receiver.max_packet_size() as usize { + return self.receiver.read_packet(buf).await; + } + + // Otherwise read a packet into the internal buffer, and return some of it to the caller + self.start = 0; + self.end = self.receiver.read_packet(&mut self.buffer).await?; + return Ok(self.read_from_buffer(buf)); + } } /// Number of stop bits for LineCoding diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index 6a5a500de..d62c67742 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" embassy-sync = { version = "0.6.2", path = "../../../../embassy-sync" } embassy-executor = { version = "0.7.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.4.0", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } -embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } +embassy-stm32 = { version = "0.2.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti", "single-bank"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } embassy-embedded-hal = { version = "0.3.0", path = "../../../../embassy-embedded-hal" } diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs index 0ab99ff90..dda2b795b 100644 --- a/examples/boot/application/stm32wb-dfu/src/main.rs +++ b/examples/boot/application/stm32wb-dfu/src/main.rs @@ -44,7 +44,7 @@ async fn main(_spawner: Spawner) { let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 64]; - let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD); + let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD, ResetImmediate); let mut builder = Builder::new( driver, config, @@ -54,7 +54,7 @@ async fn main(_spawner: Spawner) { &mut control_buf, ); - usb_dfu::<_, _, ResetImmediate>(&mut builder, &mut state, Duration::from_millis(2500)); + usb_dfu(&mut builder, &mut state, Duration::from_millis(2500)); let mut dev = builder.build(); dev.run().await diff --git a/examples/boot/application/stm32wl/src/bin/a.rs b/examples/boot/application/stm32wl/src/bin/a.rs index 127de0237..e4526927f 100644 --- a/examples/boot/application/stm32wl/src/bin/a.rs +++ b/examples/boot/application/stm32wl/src/bin/a.rs @@ -20,7 +20,7 @@ static APP_B: &[u8] = &[0, 1, 2, 3]; #[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/boot/application/stm32wl/src/bin/b.rs b/examples/boot/application/stm32wl/src/bin/b.rs index 768dadf8b..6016a9555 100644 --- a/examples/boot/application/stm32wl/src/bin/b.rs +++ b/examples/boot/application/stm32wl/src/bin/b.rs @@ -11,7 +11,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use panic_reset as _; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs index b09d53cf0..28216806e 100644 --- a/examples/boot/bootloader/stm32wb-dfu/src/main.rs +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -55,7 +55,7 @@ fn main() -> ! { let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 4096]; - let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD); + let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate); let mut builder = Builder::new( driver, config, @@ -77,7 +77,7 @@ fn main() -> ! { msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), )); - usb_dfu::<_, _, _, ResetImmediate, 4096>(&mut builder, &mut state); + usb_dfu::<_, _, _, _, 4096>(&mut builder, &mut state); let mut dev = builder.build(); embassy_futures::block_on(dev.run()); diff --git a/examples/mimxrt6/Cargo.toml b/examples/mimxrt6/Cargo.toml index 8fc510c47..b0c56f003 100644 --- a/examples/mimxrt6/Cargo.toml +++ b/examples/mimxrt6/Cargo.toml @@ -12,8 +12,8 @@ defmt-rtt = "1.0" embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } embassy-futures = { version = "0.1.1", path = "../../embassy-futures" } -embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-rtc"] } -embassy-time = { version = "0.4", path = "../../embassy-time" } +embassy-imxrt = { version = "0.1.0", path = "../../embassy-imxrt", features = ["defmt", "mimxrt685s", "unstable-pac", "time", "time-driver-os-timer"] } +embassy-time = { version = "0.4", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = "1.0.0" diff --git a/examples/mimxrt6/build.rs b/examples/mimxrt6/build.rs index 9c0ed3213..56010dfd6 100644 --- a/examples/mimxrt6/build.rs +++ b/examples/mimxrt6/build.rs @@ -25,7 +25,7 @@ fn main() { .write_all( format!( r##" -#[link_section = ".biv"] +#[unsafe(link_section = ".biv")] #[used] static BOOT_IMAGE_VERSION: u32 = 0x{:02x}{:02x}{:02x}00; "##, diff --git a/examples/mimxrt6/src/bin/crc.rs b/examples/mimxrt6/src/bin/crc.rs new file mode 100644 index 000000000..005a250e5 --- /dev/null +++ b/examples/mimxrt6/src/bin/crc.rs @@ -0,0 +1,175 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_imxrt::crc::{Config, Crc, Polynomial}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_imxrt::init(Default::default()); + let data = b"123456789"; + + info!("Initializing CRC"); + + // CRC-CCITT + let mut crc = Crc::new(p.CRC.reborrow(), Default::default()); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x29b1); + + // CRC16-ARC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xbb3d); + + // CRC16-CMS + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xaee7); + + // CRC16-DDS-110 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0x800d, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x9ecf); + + // CRC16-MAXIM-DOW + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: true, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x44c2); + + // CRC16-MODBUS + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc16, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0xffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x4b37); + + // CRC32-BZIP2 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: true, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xfc89_1918); + + // CRC32-CKSUM + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: true, + seed: 0, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x765e_7680); + + // CRC32-ISO-HDLC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: true, + reverse_out: true, + complement_out: true, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0xcbf4_3926); + + // CRC32-JAMCRC + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: true, + reverse_out: true, + complement_out: false, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x340b_c6d9); + + // CRC32-MPEG-2 + let mut crc = Crc::new( + p.CRC.reborrow(), + Config { + polynomial: Polynomial::Crc32, + reverse_in: false, + reverse_out: false, + complement_out: false, + seed: 0xffff_ffff, + ..Default::default() + }, + ); + let output = crc.feed_bytes(data); + defmt::assert_eq!(output, 0x0376_e6e7); + + info!("end program"); + cortex_m::asm::bkpt(); +} diff --git a/examples/mimxrt6/src/bin/rng.rs b/examples/mimxrt6/src/bin/rng.rs new file mode 100644 index 000000000..5f64cb96a --- /dev/null +++ b/examples/mimxrt6/src/bin/rng.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +extern crate embassy_imxrt_examples; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_imxrt::rng::Rng; +use embassy_imxrt::{bind_interrupts, peripherals, rng}; +use rand::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!("Initializing RNG"); + let mut rng = Rng::new(p.RNG, Irqs); + let mut buf = [0u8; 65]; + + // Async interface + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); + + // RngCore interface + let mut random_bytes = [0; 16]; + + let random_u32 = rng.next_u32(); + let random_u64 = rng.next_u64(); + + rng.fill_bytes(&mut random_bytes); + + info!("random_u32 {}", random_u32); + info!("random_u64 {}", random_u64); + info!("random_bytes {}", random_bytes); +} diff --git a/examples/mimxrt6/src/lib.rs b/examples/mimxrt6/src/lib.rs index da6e14427..3c3ea1981 100644 --- a/examples/mimxrt6/src/lib.rs +++ b/examples/mimxrt6/src/lib.rs @@ -6,15 +6,15 @@ use {defmt_rtt as _, panic_probe as _}; // auto-generated version information from Cargo.toml include!(concat!(env!("OUT_DIR"), "/biv.rs")); -#[link_section = ".otfad"] +#[unsafe(link_section = ".otfad")] #[used] static OTFAD: [u8; 256] = [0; 256]; #[rustfmt::skip] -#[link_section = ".fcb"] +#[unsafe(link_section = ".fcb")] #[used] static FCB: FlexSPIFlashConfigurationBlock = FlexSPIFlashConfigurationBlock::build(); -#[link_section = ".keystore"] +#[unsafe(link_section = ".keystore")] #[used] static KEYSTORE: [u8; 2048] = [0; 2048]; diff --git a/examples/nrf52840/src/bin/twim.rs b/examples/nrf52840/src/bin/twim.rs index ceaafd784..e30a3855d 100644 --- a/examples/nrf52840/src/bin/twim.rs +++ b/examples/nrf52840/src/bin/twim.rs @@ -9,6 +9,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_nrf::twim::{self, Twim}; use embassy_nrf::{bind_interrupts, peripherals}; +use static_cell::ConstStaticCell; use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x50; @@ -22,7 +23,8 @@ async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); info!("Initializing TWI..."); let config = twim::Config::default(); - let mut twi = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); + static RAM_BUFFER: ConstStaticCell<[u8; 16]> = ConstStaticCell::new([0; 16]); + let mut twi = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config, RAM_BUFFER.take()); info!("Reading..."); diff --git a/examples/nrf52840/src/bin/twim_lowpower.rs b/examples/nrf52840/src/bin/twim_lowpower.rs index 8a6f958eb..f7380e20d 100644 --- a/examples/nrf52840/src/bin/twim_lowpower.rs +++ b/examples/nrf52840/src/bin/twim_lowpower.rs @@ -30,6 +30,7 @@ async fn main(_p: Spawner) { loop { info!("Initializing TWI..."); let config = twim::Config::default(); + let mut ram_buffer = [0u8; 16]; // Create the TWIM instance with borrowed singletons, so they're not consumed. let mut twi = Twim::new( @@ -38,6 +39,7 @@ async fn main(_p: Spawner) { p.P0_03.reborrow(), p.P0_04.reborrow(), config, + &mut ram_buffer, ); info!("Reading..."); diff --git a/examples/rp/src/bin/overclock.rs b/examples/rp/src/bin/overclock.rs new file mode 100644 index 000000000..83b17308b --- /dev/null +++ b/examples/rp/src/bin/overclock.rs @@ -0,0 +1,64 @@ +//! # Overclocking the RP2040 to 200 MHz +//! +//! This example demonstrates how to configure the RP2040 to run at 200 MHz. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Set up for clock frequency of 200 MHz, setting all necessary defaults. + let config = Config::new(ClockConfig::system_freq(200_000_000).unwrap()); + + // Initialize the peripherals + let p = embassy_rp::init(config); + + // Show CPU frequency for verification + let sys_freq = clk_sys_freq(); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); + // Show core voltage for verification + let core_voltage = core_voltage().unwrap(); + info!("Core voltage: {}", core_voltage); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = Instant::now() - start; + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + counter, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/examples/rp/src/bin/overclock_manual.rs b/examples/rp/src/bin/overclock_manual.rs new file mode 100644 index 000000000..dea5cfb3c --- /dev/null +++ b/examples/rp/src/bin/overclock_manual.rs @@ -0,0 +1,81 @@ +//! # Overclocking the RP2040 to 200 MHz manually +//! +//! This example demonstrates how to manually configure the RP2040 to run at 200 MHz. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig, CoreVoltage, PllConfig}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +/// Configure the RP2040 for 200 MHz operation by manually specifying the PLL settings. +fn configure_manual_overclock() -> Config { + // Set the PLL configuration manually, starting from default values + let mut config = Config::default(); + + // Set the system clock to 200 MHz + config.clocks = ClockConfig::manual_pll( + 12_000_000, // Crystal frequency, 12 MHz is common. If using custom, set to your value. + PllConfig { + refdiv: 1, // Reference divider + fbdiv: 100, // Feedback divider + post_div1: 3, // Post divider 1 + post_div2: 2, // Post divider 2 + }, + CoreVoltage::V1_15, // Core voltage, should be set to V1_15 for 200 MHz + ); + + config +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Initialize with our manual overclock configuration + let p = embassy_rp::init(configure_manual_overclock()); + + // Show CPU frequency for verification + let sys_freq = clk_sys_freq(); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); + // Show core voltage for verification + let core_voltage = core_voltage().unwrap(); + info!("Core voltage: {}", core_voltage); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = Instant::now() - start; + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + counter, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/examples/rp/src/bin/sharing.rs b/examples/rp/src/bin/sharing.rs index 5416e20ce..497c4f845 100644 --- a/examples/rp/src/bin/sharing.rs +++ b/examples/rp/src/bin/sharing.rs @@ -31,7 +31,7 @@ use rand::RngCore; use static_cell::{ConstStaticCell, StaticCell}; use {defmt_rtt as _, panic_probe as _}; -type UartAsyncMutex = mutex::Mutex>; +type UartAsyncMutex = mutex::Mutex>; struct MyType { inner: u32, diff --git a/examples/rp/src/bin/uart_buffered_split.rs b/examples/rp/src/bin/uart_buffered_split.rs index da7e94139..3adbc18ab 100644 --- a/examples/rp/src/bin/uart_buffered_split.rs +++ b/examples/rp/src/bin/uart_buffered_split.rs @@ -48,7 +48,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: BufferedUartRx<'static, UART0>) { +async fn reader(mut rx: BufferedUartRx) { info!("Reading..."); loop { let mut buf = [0; 31]; diff --git a/examples/rp/src/bin/uart_unidir.rs b/examples/rp/src/bin/uart_unidir.rs index a45f40756..c2c8dfad8 100644 --- a/examples/rp/src/bin/uart_unidir.rs +++ b/examples/rp/src/bin/uart_unidir.rs @@ -39,7 +39,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: UartRx<'static, UART1, Async>) { +async fn reader(mut rx: UartRx<'static, Async>) { info!("Reading..."); loop { // read a total of 4 transmissions (32 / 8) and then print the result diff --git a/examples/rp235x/src/bin/blinky.rs b/examples/rp235x/src/bin/blinky.rs index a36029f92..8a2464fbb 100644 --- a/examples/rp235x/src/bin/blinky.rs +++ b/examples/rp235x/src/bin/blinky.rs @@ -14,7 +14,7 @@ use {defmt_rtt as _, panic_probe as _}; // Program metadata for `picotool info`. // This isn't needed, but it's recomended to have these minimal entries. -#[link_section = ".bi_entries"] +#[unsafe(link_section = ".bi_entries")] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), diff --git a/examples/rp235x/src/bin/blinky_wifi.rs b/examples/rp235x/src/bin/blinky_wifi.rs index ef029867a..8c352ebc4 100644 --- a/examples/rp235x/src/bin/blinky_wifi.rs +++ b/examples/rp235x/src/bin/blinky_wifi.rs @@ -18,7 +18,7 @@ use {defmt_rtt as _, panic_probe as _}; // Program metadata for `picotool info`. // This isn't needed, but it's recommended to have these minimal entries. -#[link_section = ".bi_entries"] +#[unsafe(link_section = ".bi_entries")] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), diff --git a/examples/rp235x/src/bin/blinky_wifi_pico_plus_2.rs b/examples/rp235x/src/bin/blinky_wifi_pico_plus_2.rs index 2a919a1ea..0a5bccfb3 100644 --- a/examples/rp235x/src/bin/blinky_wifi_pico_plus_2.rs +++ b/examples/rp235x/src/bin/blinky_wifi_pico_plus_2.rs @@ -18,7 +18,7 @@ use {defmt_rtt as _, panic_probe as _}; // Program metadata for `picotool info`. // This isn't needed, but it's recomended to have these minimal entries. -#[link_section = ".bi_entries"] +#[unsafe(link_section = ".bi_entries")] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ embassy_rp::binary_info::rp_program_name!(c"Blinky Example"), diff --git a/examples/rp235x/src/bin/overclock.rs b/examples/rp235x/src/bin/overclock.rs new file mode 100644 index 000000000..5fd97ef97 --- /dev/null +++ b/examples/rp235x/src/bin/overclock.rs @@ -0,0 +1,74 @@ +//! # Overclocking the RP2350 to 200 MHz +//! +//! This example demonstrates how to configure the RP2350 to run at 200 MHz instead of the default 150 MHz. +//! +//! ## Note +//! +//! As of yet there is no official support for running the RP235x at higher clock frequencies and/or other core voltages than the default. +//! Doing so may cause unexpected behavior and/or damage the chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig, CoreVoltage}; +use embassy_rp::config::Config; +use embassy_rp::gpio::{Level, Output}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + // Set up for clock frequency of 200 MHz, setting all necessary defaults. + let mut config = Config::new(ClockConfig::system_freq(200_000_000).unwrap()); + + // since for the rp235x there is no official support for higher clock frequencies, `system_freq()` will not set a voltage for us. + // We need to guess the core voltage, that is needed for the higher clock frequency. Going with a small increase from the default 1.1V here, based on + // what we know about the RP2040. This is not guaranteed to be correct. + config.clocks.core_voltage = CoreVoltage::V1_15; + + // Initialize the peripherals + let p = embassy_rp::init(config); + + // Show CPU frequency for verification + let sys_freq = clk_sys_freq(); + info!("System clock frequency: {} MHz", sys_freq / 1_000_000); + // Show core voltage for verification + let core_voltage = core_voltage().unwrap(); + info!("Core voltage: {}", core_voltage); + + // LED to indicate the system is running + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + // Reset the counter at the start of measurement period + let mut counter = 0; + + // Turn LED on while counting + led.set_high(); + + let start = Instant::now(); + + // This is a busy loop that will take some time to complete + while counter < COUNT_TO { + counter += 1; + } + + let elapsed = Instant::now() - start; + + // Report the elapsed time + led.set_low(); + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + sys_freq / 1_000_000, + counter, + elapsed.as_millis() + ); + + // Wait 2 seconds before starting the next measurement + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/examples/rp235x/src/bin/pio_rotary_encoder_rxf.rs b/examples/rp235x/src/bin/pio_rotary_encoder_rxf.rs index ccc601661..61af94560 100644 --- a/examples/rp235x/src/bin/pio_rotary_encoder_rxf.rs +++ b/examples/rp235x/src/bin/pio_rotary_encoder_rxf.rs @@ -16,7 +16,7 @@ use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, Shi use {defmt_rtt as _, panic_probe as _}; // Program metadata for `picotool info` -#[link_section = ".bi_entries"] +#[unsafe(link_section = ".bi_entries")] #[used] pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ embassy_rp::binary_info::rp_program_name!(c"example_pio_rotary_encoder_rxf"), @@ -88,8 +88,8 @@ impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { Self { sm } } - pub async fn read(&mut self) -> u32 { - self.sm.get_rxf_entry(0) + pub async fn read(&mut self) -> i32 { + self.sm.get_rxf_entry(0) as i32 } } diff --git a/examples/rp235x/src/bin/sharing.rs b/examples/rp235x/src/bin/sharing.rs index 5416e20ce..497c4f845 100644 --- a/examples/rp235x/src/bin/sharing.rs +++ b/examples/rp235x/src/bin/sharing.rs @@ -31,7 +31,7 @@ use rand::RngCore; use static_cell::{ConstStaticCell, StaticCell}; use {defmt_rtt as _, panic_probe as _}; -type UartAsyncMutex = mutex::Mutex>; +type UartAsyncMutex = mutex::Mutex>; struct MyType { inner: u32, diff --git a/examples/rp235x/src/bin/uart_buffered_split.rs b/examples/rp235x/src/bin/uart_buffered_split.rs index f707c4b5e..7cad09f9b 100644 --- a/examples/rp235x/src/bin/uart_buffered_split.rs +++ b/examples/rp235x/src/bin/uart_buffered_split.rs @@ -48,7 +48,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: BufferedUartRx<'static, UART0>) { +async fn reader(mut rx: BufferedUartRx) { info!("Reading..."); loop { let mut buf = [0; 31]; diff --git a/examples/rp235x/src/bin/uart_unidir.rs b/examples/rp235x/src/bin/uart_unidir.rs index 4e98f9e1e..45c9c8407 100644 --- a/examples/rp235x/src/bin/uart_unidir.rs +++ b/examples/rp235x/src/bin/uart_unidir.rs @@ -39,7 +39,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn reader(mut rx: UartRx<'static, UART1, Async>) { +async fn reader(mut rx: UartRx<'static, Async>) { info!("Reading..."); loop { // read a total of 4 transmissions (32 / 8) and then print the result diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 9fbe2efc3..1a46931d9 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32f777zi to your chip name, if necessary. -embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti", "single-bank"] } embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } diff --git a/examples/stm32h7/src/bin/adc_dma.rs b/examples/stm32h7/src/bin/adc_dma.rs index dc775f18a..f06b5d06e 100644 --- a/examples/stm32h7/src/bin/adc_dma.rs +++ b/examples/stm32h7/src/bin/adc_dma.rs @@ -8,7 +8,7 @@ use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".ram_d3"] +#[unsafe(link_section = ".ram_d3")] static mut DMA_BUF: [u16; 2] = [0; 2]; #[embassy_executor::main] diff --git a/examples/stm32h7/src/bin/sai.rs b/examples/stm32h7/src/bin/sai.rs index 95ffe257a..01937593a 100644 --- a/examples/stm32h7/src/bin/sai.rs +++ b/examples/stm32h7/src/bin/sai.rs @@ -16,9 +16,9 @@ const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks const SAMPLE_RATE: u32 = 48000; //DMA buffer must be in special region. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions -#[link_section = ".sram1_bss"] +#[unsafe(link_section = ".sram1_bss")] static mut TX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); -#[link_section = ".sram1_bss"] +#[unsafe(link_section = ".sram1_bss")] static mut RX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::main] @@ -112,8 +112,10 @@ async fn main(_spawner: Spawner) { let mut buf = [0u32; HALF_DMA_BUFFER_LENGTH]; loop { - sai_receiver.read(&mut buf).await.unwrap(); + // write() must be called before read() to start the master (transmitter) + // clock used by the receiver sai_transmitter.write(&buf).await.unwrap(); + sai_receiver.read(&mut buf).await.unwrap(); } } diff --git a/examples/stm32h7/src/bin/spi_bdma.rs b/examples/stm32h7/src/bin/spi_bdma.rs index 9166fe9b6..5a7dff572 100644 --- a/examples/stm32h7/src/bin/spi_bdma.rs +++ b/examples/stm32h7/src/bin/spi_bdma.rs @@ -16,7 +16,7 @@ use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; // Defined in memory.x -#[link_section = ".ram_d3"] +#[unsafe(link_section = ".ram_d3")] static mut RAM_D3: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::task] diff --git a/examples/stm32h723/src/bin/spdifrx.rs b/examples/stm32h723/src/bin/spdifrx.rs index bc8249ced..a04d7cb34 100644 --- a/examples/stm32h723/src/bin/spdifrx.rs +++ b/examples/stm32h723/src/bin/spdifrx.rs @@ -24,10 +24,10 @@ const HALF_DMA_BUFFER_LENGTH: usize = BLOCK_LENGTH * CHANNEL_COUNT; const DMA_BUFFER_LENGTH: usize = HALF_DMA_BUFFER_LENGTH * 2; // 2 half-blocks // DMA buffers must be in special regions. Refer https://embassy.dev/book/#_stm32_bdma_only_working_out_of_some_ram_regions -#[link_section = ".sram1"] +#[unsafe(link_section = ".sram1")] static mut SPDIFRX_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); -#[link_section = ".sram4"] +#[unsafe(link_section = ".sram4")] static mut SAI_BUFFER: GroundedArrayCell = GroundedArrayCell::uninit(); #[embassy_executor::main] diff --git a/examples/stm32h755cm4/src/bin/blinky.rs b/examples/stm32h755cm4/src/bin/blinky.rs index b5c547839..39112c1f5 100644 --- a/examples/stm32h755cm4/src/bin/blinky.rs +++ b/examples/stm32h755cm4/src/bin/blinky.rs @@ -10,7 +10,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".ram_d3.shared_data"] +#[unsafe(link_section = ".ram_d3.shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32h755cm7/src/bin/blinky.rs b/examples/stm32h755cm7/src/bin/blinky.rs index 94d2226c0..b30bf4de8 100644 --- a/examples/stm32h755cm7/src/bin/blinky.rs +++ b/examples/stm32h755cm7/src/bin/blinky.rs @@ -10,7 +10,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".ram_d3.shared_data"] +#[unsafe(link_section = ".ram_d3.shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index b609110af..239bfcd79 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l4s5vi to your chip name, if necessary. -embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4r5zi", "memory-x", "time-driver-any", "exti", "chrono"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4r5zi", "memory-x", "time-driver-any", "exti", "chrono", "dual-bank"] } embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index fbf68c890..4c372a554 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" [dependencies] # Change stm32l552ze to your chip name, if necessary. -embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power"] } +embassy-stm32 = { version = "0.2.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power", "dual-bank"] } embassy-sync = { version = "0.6.2", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.7.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] } embassy-time = { version = "0.4.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } diff --git a/examples/stm32wl/src/bin/blinky.rs b/examples/stm32wl/src/bin/blinky.rs index ce7d0ec58..a2a90871d 100644 --- a/examples/stm32wl/src/bin/blinky.rs +++ b/examples/stm32wl/src/bin/blinky.rs @@ -10,7 +10,7 @@ use embassy_stm32::SharedData; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/button.rs b/examples/stm32wl/src/bin/button.rs index 8b5204479..21bcd2ac6 100644 --- a/examples/stm32wl/src/bin/button.rs +++ b/examples/stm32wl/src/bin/button.rs @@ -9,7 +9,7 @@ use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[entry] diff --git a/examples/stm32wl/src/bin/button_exti.rs b/examples/stm32wl/src/bin/button_exti.rs index 8dd1a6a5e..0a8aece34 100644 --- a/examples/stm32wl/src/bin/button_exti.rs +++ b/examples/stm32wl/src/bin/button_exti.rs @@ -10,7 +10,7 @@ use embassy_stm32::gpio::Pull; use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/flash.rs b/examples/stm32wl/src/bin/flash.rs index 147f5d293..320a9723a 100644 --- a/examples/stm32wl/src/bin/flash.rs +++ b/examples/stm32wl/src/bin/flash.rs @@ -9,7 +9,7 @@ use embassy_stm32::flash::Flash; use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/random.rs b/examples/stm32wl/src/bin/random.rs index df2ed0054..68b9d7d00 100644 --- a/examples/stm32wl/src/bin/random.rs +++ b/examples/stm32wl/src/bin/random.rs @@ -14,7 +14,7 @@ bind_interrupts!(struct Irqs{ RNG => rng::InterruptHandler; }); -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/rtc.rs b/examples/stm32wl/src/bin/rtc.rs index 69a9ddc4c..d3709120f 100644 --- a/examples/stm32wl/src/bin/rtc.rs +++ b/examples/stm32wl/src/bin/rtc.rs @@ -12,7 +12,7 @@ use embassy_stm32::{Config, SharedData}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); #[embassy_executor::main] diff --git a/examples/stm32wl/src/bin/uart_async.rs b/examples/stm32wl/src/bin/uart_async.rs index ece9b9201..505a85f47 100644 --- a/examples/stm32wl/src/bin/uart_async.rs +++ b/examples/stm32wl/src/bin/uart_async.rs @@ -14,7 +14,7 @@ bind_interrupts!(struct Irqs{ LPUART1 => InterruptHandler; }); -#[link_section = ".shared_data"] +#[unsafe(link_section = ".shared_data")] static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); /* diff --git a/tests/rp/src/bin/overclock.rs b/tests/rp/src/bin/overclock.rs new file mode 100644 index 000000000..167a26eb2 --- /dev/null +++ b/tests/rp/src/bin/overclock.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] + +#[cfg(feature = "rp2040")] +teleprobe_meta::target!(b"rpi-pico"); +#[cfg(feature = "rp235xb")] +teleprobe_meta::target!(b"pimoroni-pico-plus-2"); + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::clocks::{clk_sys_freq, core_voltage, ClockConfig, CoreVoltage}; +use embassy_rp::config::Config; +use embassy_time::Instant; +use {defmt_rtt as _, panic_probe as _}; + +const COUNT_TO: i64 = 10_000_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + + // Initialize with 200MHz clock configuration + config.clocks = ClockConfig::system_freq(200_000_000).unwrap(); + + // if we are rp235x, we need to manually set the core voltage. rp2040 should do this automatically + #[cfg(feature = "rp235xb")] + { + config.clocks.core_voltage = CoreVoltage::V1_15; + } + + let _p = embassy_rp::init(config); + + // We should be at core voltage of 1.15V + assert_eq!(core_voltage().unwrap(), CoreVoltage::V1_15, "Core voltage is not 1.15V"); + // We should be at 200MHz + assert_eq!(clk_sys_freq(), 200_000_000, "System clock frequency is not 200MHz"); + + // Test the system speed + let time_elapsed = { + let mut counter = 0; + let start = Instant::now(); + while counter < COUNT_TO { + counter += 1; + } + let elapsed = Instant::now() - start; + + elapsed.as_millis() + }; + + // Tests will fail if unused variables are detected: + // Report the elapsed time, so that the compiler doesn't optimize it away for the chip not on test + info!( + "At {}Mhz: Elapsed time to count to {}: {}ms", + clk_sys_freq() / 1_000_000, + COUNT_TO, + time_elapsed + ); + + // Check if the elapsed time is within expected limits + // for rp2040 we expect about 600ms + #[cfg(feature = "rp2040")] + // allow 1% error + assert!(time_elapsed < 606, "Elapsed time is too long"); + // for rp235x we expect about 450ms + #[cfg(feature = "rp235xb")] + assert!(time_elapsed < 455, "Elapsed time is too long"); + + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/uart.rs b/tests/rp/src/bin/uart.rs index 84744ab77..80230f3fe 100644 --- a/tests/rp/src/bin/uart.rs +++ b/tests/rp/src/bin/uart.rs @@ -8,17 +8,17 @@ teleprobe_meta::target!(b"pimoroni-pico-plus-2"); use defmt::{assert_eq, *}; use embassy_executor::Spawner; use embassy_rp::gpio::{Level, Output}; -use embassy_rp::uart::{Blocking, Config, Error, Instance, Parity, Uart, UartRx}; +use embassy_rp::uart::{Blocking, Config, Error, Parity, Uart, UartRx}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; -fn read(uart: &mut Uart<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { +fn read(uart: &mut Uart<'_, Blocking>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.blocking_read(&mut buf)?; Ok(buf) } -fn read1(uart: &mut UartRx<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { +fn read1(uart: &mut UartRx<'_, Blocking>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.blocking_read(&mut buf)?; Ok(buf) diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs index d5f655e9b..cb78fc142 100644 --- a/tests/rp/src/bin/uart_buffered.rs +++ b/tests/rp/src/bin/uart_buffered.rs @@ -10,7 +10,7 @@ use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::UART0; -use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Instance, Parity}; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Parity}; use embassy_time::Timer; use embedded_io_async::{Read, ReadExactError, Write}; use {defmt_rtt as _, panic_probe as _}; @@ -19,7 +19,7 @@ bind_interrupts!(struct Irqs { UART0_IRQ => BufferedInterruptHandler; }); -async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Result<[u8; N], Error> { +async fn read(uart: &mut BufferedUart) -> Result<[u8; N], Error> { let mut buf = [255; N]; match uart.read_exact(&mut buf).await { Ok(()) => Ok(buf), @@ -29,7 +29,7 @@ async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Res } } -async fn read1(uart: &mut BufferedUartRx<'_, impl Instance>) -> Result<[u8; N], Error> { +async fn read1(uart: &mut BufferedUartRx) -> Result<[u8; N], Error> { let mut buf = [255; N]; match uart.read_exact(&mut buf).await { Ok(()) => Ok(buf), diff --git a/tests/rp/src/bin/uart_dma.rs b/tests/rp/src/bin/uart_dma.rs index a09101223..a7af81f5f 100644 --- a/tests/rp/src/bin/uart_dma.rs +++ b/tests/rp/src/bin/uart_dma.rs @@ -10,7 +10,7 @@ use embassy_executor::Spawner; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::UART0; -use embassy_rp::uart::{Async, Config, Error, Instance, InterruptHandler, Parity, Uart, UartRx}; +use embassy_rp::uart::{Async, Config, Error, InterruptHandler, Parity, Uart, UartRx}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -18,13 +18,13 @@ bind_interrupts!(struct Irqs { UART0_IRQ => InterruptHandler; }); -async fn read(uart: &mut Uart<'_, impl Instance, Async>) -> Result<[u8; N], Error> { +async fn read(uart: &mut Uart<'_, Async>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.read(&mut buf).await?; Ok(buf) } -async fn read1(uart: &mut UartRx<'_, impl Instance, Async>) -> Result<[u8; N], Error> { +async fn read1(uart: &mut UartRx<'_, Async>) -> Result<[u8; N], Error> { let mut buf = [255; N]; uart.read(&mut buf).await?; Ok(buf) diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index a676aee53..3a347e279 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -12,7 +12,7 @@ stm32f207zg = ["embassy-stm32/stm32f207zg", "spi-v1", "chrono", "not-gpdma", "et stm32f303ze = ["embassy-stm32/stm32f303ze", "chrono", "not-gpdma"] stm32f429zi = ["embassy-stm32/stm32f429zi", "spi-v1", "chrono", "eth", "stop", "can", "not-gpdma", "dac", "rng"] stm32f446re = ["embassy-stm32/stm32f446re", "spi-v1", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] -stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] +stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng", "single-bank"] stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac", "ucpd"] stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan", "cordic"] stm32h563zi = ["embassy-stm32/stm32h563zi", "spi-v345", "chrono", "eth", "rng", "fdcan", "hash", "cordic", "stop"] @@ -23,8 +23,8 @@ stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] stm32l152re = ["embassy-stm32/stm32l152re", "spi-v1", "chrono", "not-gpdma"] stm32l496zg = ["embassy-stm32/stm32l496zg", "not-gpdma", "rng"] stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"] -stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"] -stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"] +stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng", "dual-bank"] +stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash", "dual-bank"] stm32u585ai = ["embassy-stm32/stm32u585ai", "spi-v345", "chrono", "rng", "hash", "cordic"] stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "spi-v345", "chrono", "rng", "hash"] # FIXME: cordic test cause it crash stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] @@ -53,6 +53,8 @@ not-gpdma = [] dac = [] ucpd = [] cordic = ["dep:num-traits"] +dual-bank = ["embassy-stm32/dual-bank"] +single-bank = ["embassy-stm32/single-bank"] cm0 = ["portable-atomic/unsafe-assume-single-core"]