diff --git a/.github/ci/test.sh b/.github/ci/test.sh index 41da644fc..0de265049 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -8,6 +8,10 @@ export RUSTUP_HOME=/ci/cache/rustup export CARGO_HOME=/ci/cache/cargo export CARGO_TARGET_DIR=/ci/cache/target +# needed for "dumb HTTP" transport support +# used when pointing stm32-metapac to a CI-built one. +export CARGO_NET_GIT_FETCH_WITH_CLI=true + cargo test --manifest-path ./embassy-futures/Cargo.toml cargo test --manifest-path ./embassy-sync/Cargo.toml cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml @@ -21,7 +25,8 @@ cargo test --manifest-path ./embassy-boot/Cargo.toml --features ed25519-salty cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nrf52840,time-driver-rtc1,gpiote -cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features time-driver +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 diff --git a/ci.sh b/ci.sh index 15bb6eec0..50331bdba 100755 --- a/ci.sh +++ b/ci.sh @@ -2,11 +2,13 @@ set -eo pipefail -# check-cfg is stable on rustc 1.79 but not cargo 1.79. -# however, our cargo-batch is currently based on cargo 1.80, which does support check-cfg. -# so, force build.rs scripts to emit check-cfg commands. -# when 1.80 hits stable we can make build.rs unconditionally emit check-cfg and remove all this. -export EMBASSY_FORCE_CHECK_CFG=1 +if ! command -v cargo-batch &> /dev/null; then + echo "cargo-batch could not be found. Install it with the following command:" + echo "" + echo " cargo install --git https://github.com/embassy-rs/cargo-batch cargo --bin cargo-batch --locked" + echo "" + exit 1 +fi export RUSTFLAGS=-Dwarnings export DEFMT_LOG=trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info @@ -45,7 +47,7 @@ cargo batch \ --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features defmt \ --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features defmt,defmt-timestamp-uptime,generic-queue-8,mock-driver \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet,packet-trace \ - --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,igmp,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,multicast,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,dhcpv4-hostname \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ @@ -80,10 +82,13 @@ cargo batch \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,time \ - --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,defmt \ - --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,log \ - --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,intrinsics \ - --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,defmt,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,log,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,intrinsics,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,qspi-as-gpio,rp2040 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,defmt,rp235xa \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv8m.main-none-eabihf --features time-driver,log,rp235xa \ + --- build --release --manifest-path embassy-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 \ @@ -171,13 +176,17 @@ cargo batch \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs' \ --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \ - --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \ - --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs,bluetooth' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs,bluetooth' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'embassy-rp/rp2040,overclock' \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \ - --- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi \ - --- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \ + --- build --release --manifest-path embassy-boot-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \ + --- build --release --manifest-path embassy-boot-rp/Cargo.toml --target thumbv6m-none-eabi --features embassy-rp/rp2040 \ + --- build --release --manifest-path embassy-boot-stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \ --- build --release --manifest-path docs/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ @@ -191,6 +200,7 @@ cargo batch \ --- build --release --manifest-path examples/nrf9151/ns/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9151/ns \ --- build --release --manifest-path examples/nrf51/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/nrf51 \ --- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \ + --- build --release --manifest-path examples/rp23/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/rp23 \ --- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \ --- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \ --- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \ @@ -205,6 +215,8 @@ cargo batch \ --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32h5 \ --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \ --- build --release --manifest-path examples/stm32h735/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h735 \ + --- build --release --manifest-path examples/stm32h755cm4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm4 \ + --- build --release --manifest-path examples/stm32h755cm7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h755cm7 \ --- build --release --manifest-path examples/stm32h7rs/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7rs \ --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \ --- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \ @@ -218,6 +230,8 @@ cargo batch \ --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \ --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf9160 \ --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns,skip-include --out-dir out/examples/boot/nrf9120 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns,skip-include --out-dir out/examples/boot/nrf9151 \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns,skip-include --out-dir out/examples/boot/nrf9161 \ --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \ --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \ --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \ @@ -230,10 +244,12 @@ cargo batch \ --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9120-ns \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9151-ns \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9161-ns \ --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ - --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ + --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32l496zg \ --- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg \ - --- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h747xi-cm7 \ + --- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h743zi \ --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ @@ -274,6 +290,10 @@ cargo batch \ $BUILD_EXTRA +# temporarily disabled, these boards are dead. +rm -rf out/tests/stm32f103c8 +rm -rf out/tests/nrf52840-dk + rm out/tests/stm32wb55rg/wpan_mac rm out/tests/stm32wb55rg/wpan_ble @@ -283,8 +303,10 @@ rm out/tests/stm32f207zg/eth # doesn't work, gives "noise error", no idea why. usart_dma does pass. rm out/tests/stm32u5a5zj/usart -# flaky, perhaps bad wire +# flaky, probably due to bad ringbuffered dma code. rm out/tests/stm32l152re/usart_rx_ringbuffered +rm out/tests/stm32f207zg/usart_rx_ringbuffered +rm out/tests/stm32wl55jc/usart_rx_ringbuffered if [[ -z "${TELEPROBE_TOKEN-}" ]]; then echo No teleprobe token found, skipping running HIL tests diff --git a/cyw43-firmware/43439A0.bin b/cyw43-firmware/43439A0.bin old mode 100755 new mode 100644 index 017375277..a05482fe9 Binary files a/cyw43-firmware/43439A0.bin and b/cyw43-firmware/43439A0.bin differ diff --git a/cyw43-firmware/43439A0_btfw.bin b/cyw43-firmware/43439A0_btfw.bin new file mode 100644 index 000000000..290ce8ef0 Binary files /dev/null and b/cyw43-firmware/43439A0_btfw.bin differ diff --git a/cyw43-firmware/43439A0_clm.bin b/cyw43-firmware/43439A0_clm.bin old mode 100755 new mode 100644 index 1fedd753a..dc4ee0252 Binary files a/cyw43-firmware/43439A0_clm.bin and b/cyw43-firmware/43439A0_clm.bin differ diff --git a/cyw43-firmware/README.md b/cyw43-firmware/README.md index db3d9c9cf..10a6b5d02 100644 --- a/cyw43-firmware/README.md +++ b/cyw43-firmware/README.md @@ -1,9 +1,14 @@ -# WiFi firmware +# WiFi + Bluetooth firmware blobs -Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439 +Firmware obtained from https://github.com/georgerobotics/cyw43-driver/tree/main/firmware Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) ## Changelog -* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 ot 7.95.62 +* 2023-08-21: synced with `a1dc885` - Update 43439 fw + clm to come from `wb43439A0_7_95_49_00_combined.h` + add Bluetooth firmware +* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 to 7.95.62 + +## Notes + +If you update these files, please update the lengths in the `tests/rp/src/bin/cyw43_perf.rs` test (which relies on these files running from RAM). diff --git a/cyw43-pio/CHANGELOG.md b/cyw43-pio/CHANGELOG.md new file mode 100644 index 000000000..913f3515a --- /dev/null +++ b/cyw43-pio/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Update to cyw43 0.2.0 +- Update to embassy-rp 0.2.0 + +## 0.1.0 - 2024-01-11 + +- First release diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml index 157046b18..4e21c255f 100644 --- a/cyw43-pio/Cargo.toml +++ b/cyw43-pio/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cyw43-pio" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = "RP2040 PIO SPI implementation for cyw43" keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] @@ -15,8 +15,8 @@ documentation = "https://docs.embassy.dev/cyw43-pio" overclock = [] [dependencies] -cyw43 = { version = "0.1.0", path = "../cyw43" } -embassy-rp = { version = "0.1.0", path = "../embassy-rp" } +cyw43 = { version = "0.2.0", path = "../cyw43" } +embassy-rp = { version = "0.2.0", path = "../embassy-rp" } pio-proc = "0.2" pio = "0.2.1" fixed = "1.23.1" @@ -26,3 +26,4 @@ defmt = { version = "0.3", optional = true } src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/" target = "thumbv6m-none-eabi" +features = ["embassy-rp/rp2040"] diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 8c217b995..40cf63a17 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -181,7 +181,10 @@ where let read_bits = read.len() * 32 + 32 - 1; #[cfg(feature = "defmt")] - defmt::trace!("write={} read={}", write_bits, read_bits); + defmt::trace!("cmd_read write={} read={}", write_bits, read_bits); + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len()); unsafe { instr::set_y(&mut self.sm, read_bits as u32); @@ -201,6 +204,10 @@ where .rx() .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) .await; + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read cmd = {:02x} len = {} read = {:08x}", cmd, read.len(), read); + status } } diff --git a/cyw43/CHANGELOG.md b/cyw43/CHANGELOG.md new file mode 100644 index 000000000..1859f7317 --- /dev/null +++ b/cyw43/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Update to new versions of embassy-{time,sync} +- Add more fields to the BssInfo packet struct #2461 +- Extend the Scan API #2282 +- Reuse buf to reduce stack usage #2580 +- Add MAC address getter to cyw43 controller #2818 +- Add function to join WPA2 network with precomputed PSK. #2885 +- Add function to close soft AP. #3042 +- Fixing missing re-export #3211 + +## 0.1.0 - 2024-01-11 + +- First release diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index c613698c1..751e59a69 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cyw43" -version = "0.1.0" +version = "0.2.0" edition = "2021" description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W." keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] @@ -10,17 +10,18 @@ repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/cyw43" [features] -defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt"] +defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt", "bt-hci?/defmt", "embedded-io-async?/defmt-03"] log = ["dep:log"] +bluetooth = ["dep:bt-hci", "dep:embedded-io-async"] # Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. firmware-logs = [] [dependencies] -embassy-time = { version = "0.3.1", path = "../embassy-time"} +embassy-time = { version = "0.3.2", path = "../embassy-time"} embassy-sync = { version = "0.6.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"} -embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} defmt = { version = "0.3", optional = true } log = { version = "0.4.17", optional = true } @@ -31,9 +32,12 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-hal-1 = { package = "embedded-hal", version = "1.0" } num_enum = { version = "0.5.7", default-features = false } - heapless = "0.8.0" +# Bluetooth deps +embedded-io-async = { version = "0.6.0", optional = true } +bt-hci = { version = "0.1.0", optional = true } + [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" diff --git a/cyw43/src/bluetooth.rs b/cyw43/src/bluetooth.rs new file mode 100644 index 000000000..f617a8c7e --- /dev/null +++ b/cyw43/src/bluetooth.rs @@ -0,0 +1,508 @@ +use core::cell::RefCell; +use core::future::Future; +use core::mem::MaybeUninit; + +use bt_hci::transport::WithIndicator; +use bt_hci::{ControllerToHostPacket, FromHciBytes, HostToControllerPacket, PacketKind, WriteHci}; +use embassy_futures::yield_now; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::zerocopy_channel; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::digital::OutputPin; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::util::round_up; +use crate::{util, CHIP}; + +pub(crate) struct BtState { + rx: [BtPacketBuf; 4], + tx: [BtPacketBuf; 4], + inner: MaybeUninit>, +} + +impl BtState { + pub const fn new() -> Self { + Self { + rx: [const { BtPacketBuf::new() }; 4], + tx: [const { BtPacketBuf::new() }; 4], + inner: MaybeUninit::uninit(), + } + } +} + +struct BtStateInnre<'d> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>, +} + +/// Bluetooth driver. +pub struct BtDriver<'d> { + rx: RefCell>, + tx: RefCell>, +} + +pub(crate) struct BtRunner<'d> { + pub(crate) tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, BtPacketBuf>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, BtPacketBuf>, + + // Bluetooth circular buffers + addr: u32, + h2b_write_pointer: u32, + b2h_read_pointer: u32, +} + +const BT_HCI_MTU: usize = 1024; + +/// Represents a packet of size MTU. +pub(crate) struct BtPacketBuf { + pub(crate) len: usize, + pub(crate) buf: [u8; BT_HCI_MTU], +} + +impl BtPacketBuf { + /// Create a new packet buffer. + pub const fn new() -> Self { + Self { + len: 0, + buf: [0; BT_HCI_MTU], + } + } +} + +pub(crate) fn new<'d>(state: &'d mut BtState) -> (BtRunner<'d>, BtDriver<'d>) { + // safety: this is a self-referential struct, however: + // - it can't move while the `'d` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let state_uninit: *mut MaybeUninit> = + (&mut state.inner as *mut MaybeUninit>).cast(); + let state = unsafe { &mut *state_uninit }.write(BtStateInnre { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + BtRunner { + tx_chan: tx_receiver, + rx_chan: rx_sender, + + addr: 0, + h2b_write_pointer: 0, + b2h_read_pointer: 0, + }, + BtDriver { + rx: RefCell::new(rx_receiver), + tx: RefCell::new(tx_sender), + }, + ) +} + +pub(crate) struct CybtFwCb<'a> { + pub p_next_line_start: &'a [u8], +} + +pub(crate) struct HexFileData<'a> { + pub addr_mode: i32, + pub hi_addr: u16, + pub dest_addr: u32, + pub p_ds: &'a mut [u8], +} + +pub(crate) fn read_firmware_patch_line(p_btfw_cb: &mut CybtFwCb, hfd: &mut HexFileData) -> u32 { + let mut abs_base_addr32 = 0; + + loop { + let num_bytes = p_btfw_cb.p_next_line_start[0]; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..]; + + let addr = (p_btfw_cb.p_next_line_start[0] as u16) << 8 | p_btfw_cb.p_next_line_start[1] as u16; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[2..]; + + let line_type = p_btfw_cb.p_next_line_start[0]; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..]; + + if num_bytes == 0 { + break; + } + + hfd.p_ds[..num_bytes as usize].copy_from_slice(&p_btfw_cb.p_next_line_start[..num_bytes as usize]); + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[num_bytes as usize..]; + + match line_type { + BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS => { + hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16; + hfd.addr_mode = BTFW_ADDR_MODE_EXTENDED; + } + BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS => { + hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16; + hfd.addr_mode = BTFW_ADDR_MODE_SEGMENT; + } + BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS => { + abs_base_addr32 = (hfd.p_ds[0] as u32) << 24 + | (hfd.p_ds[1] as u32) << 16 + | (hfd.p_ds[2] as u32) << 8 + | hfd.p_ds[3] as u32; + hfd.addr_mode = BTFW_ADDR_MODE_LINEAR32; + } + BTFW_HEX_LINE_TYPE_DATA => { + hfd.dest_addr = addr as u32; + match hfd.addr_mode { + BTFW_ADDR_MODE_EXTENDED => hfd.dest_addr += (hfd.hi_addr as u32) << 16, + BTFW_ADDR_MODE_SEGMENT => hfd.dest_addr += (hfd.hi_addr as u32) << 4, + BTFW_ADDR_MODE_LINEAR32 => hfd.dest_addr += abs_base_addr32, + _ => {} + } + return num_bytes as u32; + } + _ => {} + } + } + 0 +} + +impl<'a> BtRunner<'a> { + pub(crate) async fn init_bluetooth(&mut self, bus: &mut Bus, firmware: &[u8]) { + trace!("init_bluetooth"); + bus.bp_write32(CHIP.bluetooth_base_address + BT2WLAN_PWRUP_ADDR, BT2WLAN_PWRUP_WAKE) + .await; + Timer::after(Duration::from_millis(2)).await; + self.upload_bluetooth_firmware(bus, firmware).await; + self.wait_bt_ready(bus).await; + self.init_bt_buffers(bus).await; + self.wait_bt_awake(bus).await; + self.bt_set_host_ready(bus).await; + self.bt_toggle_intr(bus).await; + } + + pub(crate) async fn upload_bluetooth_firmware( + &mut self, + bus: &mut Bus, + firmware: &[u8], + ) { + // read version + let version_length = firmware[0]; + let _version = &firmware[1..=version_length as usize]; + // skip version + 1 extra byte as per cybt_shared_bus_driver.c + let firmware = &firmware[version_length as usize + 2..]; + // buffers + let mut data_buffer: [u8; 0x100] = [0; 0x100]; + let mut aligned_data_buffer: [u8; 0x100] = [0; 0x100]; + // structs + let mut btfw_cb = CybtFwCb { + p_next_line_start: firmware, + }; + let mut hfd = HexFileData { + addr_mode: BTFW_ADDR_MODE_EXTENDED, + hi_addr: 0, + dest_addr: 0, + p_ds: &mut data_buffer, + }; + loop { + let num_fw_bytes = read_firmware_patch_line(&mut btfw_cb, &mut hfd); + if num_fw_bytes == 0 { + break; + } + let fw_bytes = &hfd.p_ds[0..num_fw_bytes as usize]; + let mut dest_start_addr = hfd.dest_addr + CHIP.bluetooth_base_address; + let mut aligned_data_buffer_index: usize = 0; + // pad start + if !util::is_aligned(dest_start_addr, 4) { + let num_pad_bytes = dest_start_addr % 4; + let padded_dest_start_addr = util::round_down(dest_start_addr, 4); + let memory_value = bus.bp_read32(padded_dest_start_addr).await; + let memory_value_bytes = memory_value.to_le_bytes(); + // Copy the previous memory value's bytes to the start + for i in 0..num_pad_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i]; + aligned_data_buffer_index += 1; + } + // Copy the firmware bytes after the padding bytes + for i in 0..num_fw_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i]; + aligned_data_buffer_index += 1; + } + dest_start_addr = padded_dest_start_addr; + } else { + // Directly copy fw_bytes into aligned_data_buffer if no start padding is required + for i in 0..num_fw_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i]; + aligned_data_buffer_index += 1; + } + } + // pad end + let mut dest_end_addr = dest_start_addr + aligned_data_buffer_index as u32; + if !util::is_aligned(dest_end_addr, 4) { + let offset = dest_end_addr % 4; + let num_pad_bytes_end = 4 - offset; + let padded_dest_end_addr = util::round_down(dest_end_addr, 4); + let memory_value = bus.bp_read32(padded_dest_end_addr).await; + let memory_value_bytes = memory_value.to_le_bytes(); + // Append the necessary memory bytes to pad the end of aligned_data_buffer + for i in offset..4 { + aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i as usize]; + aligned_data_buffer_index += 1; + } + dest_end_addr += num_pad_bytes_end; + } else { + // pad end alignment not needed + } + let buffer_to_write = &aligned_data_buffer[0..aligned_data_buffer_index as usize]; + assert!(dest_start_addr % 4 == 0); + assert!(dest_end_addr % 4 == 0); + assert!(aligned_data_buffer_index % 4 == 0); + bus.bp_write(dest_start_addr, buffer_to_write).await; + } + } + + pub(crate) async fn wait_bt_ready(&mut self, bus: &mut Bus) { + trace!("wait_bt_ready"); + let mut success = false; + for _ in 0..300 { + let val = bus.bp_read32(BT_CTRL_REG_ADDR).await; + trace!("BT_CTRL_REG_ADDR = {:08x}", val); + if val & BTSDIO_REG_FW_RDY_BITMASK != 0 { + success = true; + break; + } + Timer::after(Duration::from_millis(1)).await; + } + assert!(success == true); + } + + pub(crate) async fn wait_bt_awake(&mut self, bus: &mut Bus) { + trace!("wait_bt_awake"); + let mut success = false; + for _ in 0..300 { + let val = bus.bp_read32(BT_CTRL_REG_ADDR).await; + trace!("BT_CTRL_REG_ADDR = {:08x}", val); + if val & BTSDIO_REG_BT_AWAKE_BITMASK != 0 { + success = true; + break; + } + Timer::after(Duration::from_millis(1)).await; + } + assert!(success == true); + } + + pub(crate) async fn bt_set_host_ready(&mut self, bus: &mut Bus) { + trace!("bt_set_host_ready"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = old_val | BTSDIO_REG_SW_RDY_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + // TODO: use this + #[allow(dead_code)] + pub(crate) async fn bt_set_awake(&mut self, bus: &mut Bus, awake: bool) { + trace!("bt_set_awake"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = if awake { + old_val | BTSDIO_REG_WAKE_BT_BITMASK + } else { + old_val & !BTSDIO_REG_WAKE_BT_BITMASK + }; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + pub(crate) async fn bt_toggle_intr(&mut self, bus: &mut Bus) { + trace!("bt_toggle_intr"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = old_val ^ BTSDIO_REG_DATA_VALID_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + // TODO: use this + #[allow(dead_code)] + pub(crate) async fn bt_set_intr(&mut self, bus: &mut Bus) { + trace!("bt_set_intr"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + let new_val = old_val | BTSDIO_REG_DATA_VALID_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + pub(crate) async fn init_bt_buffers(&mut self, bus: &mut Bus) { + trace!("init_bt_buffers"); + self.addr = bus.bp_read32(WLAN_RAM_BASE_REG_ADDR).await; + assert!(self.addr != 0); + trace!("wlan_ram_base_addr = {:08x}", self.addr); + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_IN, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, 0).await; + } + + async fn bt_bus_request(&mut self, bus: &mut Bus) { + // TODO: CYW43_THREAD_ENTER mutex? + self.bt_set_awake(bus, true).await; + self.wait_bt_awake(bus).await; + } + + pub(crate) async fn hci_write(&mut self, bus: &mut Bus) { + self.bt_bus_request(bus).await; + + // NOTE(unwrap): we only call this when we do have a packet in the queue. + let buf = self.tx_chan.try_receive().unwrap(); + debug!("HCI tx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len])); + + let len = buf.len as u32 - 1; // len doesn't include hci type byte + let rounded_len = round_up(len, 4); + let total_len = 4 + rounded_len; + + let read_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT).await; + let available = read_pointer.wrapping_sub(self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE; + if available < total_len { + warn!( + "bluetooth tx queue full, retrying. len {} available {}", + total_len, available + ); + yield_now().await; + return; + } + + // Build header + let mut header = [0u8; 4]; + header[0] = len as u8; + header[1] = (len >> 8) as u8; + header[2] = (len >> 16) as u8; + header[3] = buf.buf[0]; // HCI type byte + + // Write header + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, &header).await; + self.h2b_write_pointer = (self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE; + + // Write payload. + let payload = &buf.buf[1..][..rounded_len as usize]; + if self.h2b_write_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize { + // wraparound + let n = BTSDIO_FWBUF_SIZE - self.h2b_write_pointer; + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, &payload[..n as usize]).await; + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF; + bus.bp_write(addr, &payload[n as usize..]).await; + } else { + // no wraparound + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, payload).await; + } + self.h2b_write_pointer = (self.h2b_write_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE; + + // Update pointer. + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, self.h2b_write_pointer) + .await; + + self.bt_toggle_intr(bus).await; + + self.tx_chan.receive_done(); + } + + async fn bt_has_work(&mut self, bus: &mut Bus) -> bool { + let int_status = bus.bp_read32(CHIP.sdiod_core_base_address + SDIO_INT_STATUS).await; + if int_status & I_HMB_FC_CHANGE != 0 { + bus.bp_write32( + CHIP.sdiod_core_base_address + SDIO_INT_STATUS, + int_status & I_HMB_FC_CHANGE, + ) + .await; + return true; + } + return false; + } + + pub(crate) async fn handle_irq(&mut self, bus: &mut Bus) { + if self.bt_has_work(bus).await { + loop { + // Check if we have data. + let write_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_BT2HOST_IN).await; + let available = write_pointer.wrapping_sub(self.b2h_read_pointer) % BTSDIO_FWBUF_SIZE; + if available == 0 { + break; + } + + // read header + let mut header = [0u8; 4]; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, &mut header).await; + + // calc length + let len = header[0] as u32 | ((header[1]) as u32) << 8 | ((header[2]) as u32) << 16; + let rounded_len = round_up(len, 4); + if available < 4 + rounded_len { + warn!("ringbuf data not enough for a full packet?"); + break; + } + self.b2h_read_pointer = (self.b2h_read_pointer + 4) % BTSDIO_FWBUF_SIZE; + + // Obtain a buf from the channel. + let buf = self.rx_chan.send().await; + + buf.buf[0] = header[3]; // hci packet type + let payload = &mut buf.buf[1..][..rounded_len as usize]; + if self.b2h_read_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize { + // wraparound + let n = BTSDIO_FWBUF_SIZE - self.b2h_read_pointer; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, &mut payload[..n as usize]).await; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF; + bus.bp_read(addr, &mut payload[n as usize..]).await; + } else { + // no wraparound + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, payload).await; + } + self.b2h_read_pointer = (self.b2h_read_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, self.b2h_read_pointer) + .await; + + buf.len = 1 + len as usize; + debug!("HCI rx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len])); + + self.rx_chan.send_done(); + + self.bt_toggle_intr(bus).await; + } + } + } +} + +impl<'d> embedded_io_async::ErrorType for BtDriver<'d> { + type Error = core::convert::Infallible; +} + +impl<'d> bt_hci::transport::Transport for BtDriver<'d> { + fn read<'a>(&self, rx: &'a mut [u8]) -> impl Future, Self::Error>> { + async { + let ch = &mut *self.rx.borrow_mut(); + let buf = ch.receive().await; + let n = buf.len; + assert!(n < rx.len()); + rx[..n].copy_from_slice(&buf.buf[..n]); + ch.receive_done(); + + let kind = PacketKind::from_hci_bytes_complete(&rx[..1]).unwrap(); + let (res, _) = ControllerToHostPacket::from_hci_bytes_with_kind(kind, &rx[1..n]).unwrap(); + Ok(res) + } + } + + /// Write a complete HCI packet from the tx buffer + fn write(&self, val: &T) -> impl Future> { + async { + let ch = &mut *self.tx.borrow_mut(); + let buf = ch.send().await; + let buf_len = buf.buf.len(); + let mut slice = &mut buf.buf[..]; + WithIndicator::new(val).write_hci(&mut slice).unwrap(); + buf.len = buf_len - slice.len(); + ch.send_done(); + Ok(()) + } + } +} diff --git a/cyw43/src/bus.rs b/cyw43/src/bus.rs index 014109038..8a53484d5 100644 --- a/cyw43/src/bus.rs +++ b/cyw43/src/bus.rs @@ -4,7 +4,7 @@ use embedded_hal_1::digital::OutputPin; use futures::FutureExt; use crate::consts::*; -use crate::slice8_mut; +use crate::util::slice8_mut; /// Custom Spi Trait that _only_ supports the bus operation of the cyw43 /// Implementors are expected to hold the CS pin low during an operation. @@ -48,44 +48,91 @@ where } } - pub async fn init(&mut self) { + pub async fn init(&mut self, bluetooth_enabled: bool) { // Reset + trace!("WL_REG off/on"); self.pwr.set_low().unwrap(); Timer::after_millis(20).await; self.pwr.set_high().unwrap(); Timer::after_millis(250).await; + trace!("read REG_BUS_TEST_RO"); while self - .read32_swapped(REG_BUS_TEST_RO) + .read32_swapped(FUNC_BUS, REG_BUS_TEST_RO) .inspect(|v| trace!("{:#x}", v)) .await != FEEDBEAD {} - self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; - let val = self.read32_swapped(REG_BUS_TEST_RW).await; + trace!("write REG_BUS_TEST_RW"); + self.write32_swapped(FUNC_BUS, REG_BUS_TEST_RW, TEST_PATTERN).await; + let val = self.read32_swapped(FUNC_BUS, REG_BUS_TEST_RW).await; trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); - let val = self.read32_swapped(REG_BUS_CTRL).await; + trace!("read REG_BUS_CTRL"); + let val = self.read32_swapped(FUNC_BUS, REG_BUS_CTRL).await; trace!("{:#010b}", (val & 0xff)); // 32-bit word length, little endian (which is the default endianess). + // TODO: C library is uint32_t val = WORD_LENGTH_32 | HIGH_SPEED_MODE| ENDIAN_BIG | INTERRUPT_POLARITY_HIGH | WAKE_UP | 0x4 << (8 * SPI_RESPONSE_DELAY) | INTR_WITH_STATUS << (8 * SPI_STATUS_ENABLE); + trace!("write REG_BUS_CTRL"); self.write32_swapped( + FUNC_BUS, REG_BUS_CTRL, - WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE | INTERRUPT_WITH_STATUS, + WORD_LENGTH_32 + | HIGH_SPEED + | INTERRUPT_POLARITY_HIGH + | WAKE_UP + | 0x4 << (8 * REG_BUS_RESPONSE_DELAY) + | STATUS_ENABLE << (8 * REG_BUS_STATUS_ENABLE) + | INTR_WITH_STATUS << (8 * REG_BUS_STATUS_ENABLE), ) .await; + trace!("read REG_BUS_CTRL"); let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; trace!("{:#b}", val); + // TODO: C doesn't do this? i doubt it messes anything up + trace!("read REG_BUS_TEST_RO"); let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; trace!("{:#x}", val); assert_eq!(val, FEEDBEAD); + + // TODO: C doesn't do this? i doubt it messes anything up + trace!("read REG_BUS_TEST_RW"); let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; trace!("{:#x}", val); assert_eq!(val, TEST_PATTERN); + + trace!("write SPI_RESP_DELAY_F1 CYW43_BACKPLANE_READ_PAD_LEN_BYTES"); + self.write8(FUNC_BUS, SPI_RESP_DELAY_F1, WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE) + .await; + + // TODO: Make sure error interrupt bits are clear? + // cyw43_write_reg_u8(self, BUS_FUNCTION, SPI_INTERRUPT_REGISTER, DATA_UNAVAILABLE | COMMAND_ERROR | DATA_ERROR | F1_OVERFLOW) != 0) + trace!("Make sure error interrupt bits are clear"); + self.write8( + FUNC_BUS, + REG_BUS_INTERRUPT, + (IRQ_DATA_UNAVAILABLE | IRQ_COMMAND_ERROR | IRQ_DATA_ERROR | IRQ_F1_OVERFLOW) as u8, + ) + .await; + + // Enable a selection of interrupts + // TODO: why not all of these F2_F3_FIFO_RD_UNDERFLOW | F2_F3_FIFO_WR_OVERFLOW | COMMAND_ERROR | DATA_ERROR | F2_PACKET_AVAILABLE | F1_OVERFLOW | F1_INTR + trace!("enable a selection of interrupts"); + let mut val = IRQ_F2_F3_FIFO_RD_UNDERFLOW + | IRQ_F2_F3_FIFO_WR_OVERFLOW + | IRQ_COMMAND_ERROR + | IRQ_DATA_ERROR + | IRQ_F2_PACKET_AVAILABLE + | IRQ_F1_OVERFLOW; + if bluetooth_enabled { + val = val | IRQ_F1_INTR; + } + self.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, val).await; } pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { @@ -107,6 +154,8 @@ where #[allow(unused)] pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + trace!("bp_read addr = {:08x}", addr); + // It seems the HW force-aligns the addr // to 2 if data.len() >= 2 // to 4 if data.len() >= 4 @@ -140,6 +189,8 @@ where } pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { + trace!("bp_write addr = {:08x}", addr); + // It seems the HW force-aligns the addr // to 2 if data.len() >= 2 // to 4 if data.len() >= 4 @@ -196,23 +247,32 @@ where } async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { + trace!("backplane_readn addr = {:08x} len = {}", addr, len); + self.backplane_set_window(addr).await; let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; if len == 4 { - bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; } - self.readn(FUNC_BACKPLANE, bus_addr, len).await + + let val = self.readn(FUNC_BACKPLANE, bus_addr, len).await; + + trace!("backplane_readn addr = {:08x} len = {} val = {:08x}", addr, len, val); + + return val; } async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { + trace!("backplane_writen addr = {:08x} len = {} val = {:08x}", addr, len, val); + self.backplane_set_window(addr).await; let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; if len == 4 { - bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; } - self.writen(FUNC_BACKPLANE, bus_addr, val, len).await + self.writen(FUNC_BACKPLANE, bus_addr, val, len).await; } async fn backplane_set_window(&mut self, addr: u32) { @@ -293,8 +353,8 @@ where self.status = self.spi.cmd_write(&[cmd, val]).await; } - async fn read32_swapped(&mut self, addr: u32) -> u32 { - let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); + async fn read32_swapped(&mut self, func: u32, addr: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, func, addr, 4); let cmd = swap16(cmd); let mut buf = [0; 1]; @@ -303,8 +363,8 @@ where swap16(buf[0]) } - async fn write32_swapped(&mut self, addr: u32, val: u32) { - let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); + async fn write32_swapped(&mut self, func: u32, addr: u32, val: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, 4); let buf = [swap16(cmd), swap16(val)]; self.status = self.spi.cmd_write(&buf).await; diff --git a/cyw43/src/consts.rs b/cyw43/src/consts.rs index 4e2836f3b..c3f0dbfd8 100644 --- a/cyw43/src/consts.rs +++ b/cyw43/src/consts.rs @@ -5,19 +5,33 @@ pub(crate) const FUNC_BACKPLANE: u32 = 1; pub(crate) const FUNC_WLAN: u32 = 2; pub(crate) const FUNC_BT: u32 = 3; +// Register addresses pub(crate) const REG_BUS_CTRL: u32 = 0x0; +pub(crate) const REG_BUS_RESPONSE_DELAY: u32 = 0x1; +pub(crate) const REG_BUS_STATUS_ENABLE: u32 = 0x2; pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask pub(crate) const REG_BUS_STATUS: u32 = 0x8; pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; + +// SPI_BUS_CONTROL Bits pub(crate) const WORD_LENGTH_32: u32 = 0x1; +pub(crate) const ENDIAN_BIG: u32 = 0x2; +pub(crate) const CLOCK_PHASE: u32 = 0x4; +pub(crate) const CLOCK_POLARITY: u32 = 0x8; pub(crate) const HIGH_SPEED: u32 = 0x10; -pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5; -pub(crate) const WAKE_UP: u32 = 1 << 7; -pub(crate) const STATUS_ENABLE: u32 = 1 << 16; -pub(crate) const INTERRUPT_WITH_STATUS: u32 = 1 << 17; +pub(crate) const INTERRUPT_POLARITY_HIGH: u32 = 0x20; +pub(crate) const WAKE_UP: u32 = 0x80; + +// SPI_STATUS_ENABLE bits +pub(crate) const STATUS_ENABLE: u32 = 0x01; +pub(crate) const INTR_WITH_STATUS: u32 = 0x02; +pub(crate) const RESP_DELAY_ALL: u32 = 0x04; +pub(crate) const DWORD_PKT_LEN_EN: u32 = 0x08; +pub(crate) const CMD_ERR_CHK_EN: u32 = 0x20; +pub(crate) const DATA_ERR_CHK_EN: u32 = 0x40; // SPI_STATUS_REGISTER bits pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; @@ -51,6 +65,13 @@ pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; +pub(crate) const I_HMB_SW_MASK: u32 = 0x000000f0; +pub(crate) const I_HMB_FC_CHANGE: u32 = 1 << 5; +pub(crate) const SDIO_INT_STATUS: u32 = 0x20; +pub(crate) const SDIO_INT_HOST_MASK: u32 = 0x24; + +pub(crate) const SPI_F2_WATERMARK: u8 = 0x20; + pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; @@ -92,17 +113,6 @@ pub(crate) const IRQ_F1_INTR: u16 = 0x2000; pub(crate) const IRQ_F2_INTR: u16 = 0x4000; pub(crate) const IRQ_F3_INTR: u16 = 0x8000; -pub(crate) const IOCTL_CMD_UP: u32 = 2; -pub(crate) const IOCTL_CMD_DOWN: u32 = 3; -pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; -pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; -pub(crate) const IOCTL_CMD_DISASSOC: u32 = 52; -pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; -pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; -pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; -pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; -pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; - pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; @@ -119,6 +129,44 @@ pub(crate) const WPA2_SECURITY: u32 = 0x00400000; pub(crate) const MIN_PSK_LEN: usize = 8; pub(crate) const MAX_PSK_LEN: usize = 64; +// Bluetooth firmware extraction constants. +pub(crate) const BTFW_ADDR_MODE_UNKNOWN: i32 = 0; +pub(crate) const BTFW_ADDR_MODE_EXTENDED: i32 = 1; +pub(crate) const BTFW_ADDR_MODE_SEGMENT: i32 = 2; +pub(crate) const BTFW_ADDR_MODE_LINEAR32: i32 = 3; + +pub(crate) const BTFW_HEX_LINE_TYPE_DATA: u8 = 0; +pub(crate) const BTFW_HEX_LINE_TYPE_END_OF_DATA: u8 = 1; +pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS: u8 = 2; +pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS: u8 = 4; +pub(crate) const BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS: u8 = 5; + +// Bluetooth constants. +pub(crate) const SPI_RESP_DELAY_F1: u32 = 0x001d; +pub(crate) const WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE: u8 = 4; + +pub(crate) const BT2WLAN_PWRUP_WAKE: u32 = 3; +pub(crate) const BT2WLAN_PWRUP_ADDR: u32 = 0x640894; + +pub(crate) const BT_CTRL_REG_ADDR: u32 = 0x18000c7c; +pub(crate) const HOST_CTRL_REG_ADDR: u32 = 0x18000d6c; +pub(crate) const WLAN_RAM_BASE_REG_ADDR: u32 = 0x18000d68; + +pub(crate) const BTSDIO_REG_DATA_VALID_BITMASK: u32 = 1 << 1; +pub(crate) const BTSDIO_REG_BT_AWAKE_BITMASK: u32 = 1 << 8; +pub(crate) const BTSDIO_REG_WAKE_BT_BITMASK: u32 = 1 << 17; +pub(crate) const BTSDIO_REG_SW_RDY_BITMASK: u32 = 1 << 24; +pub(crate) const BTSDIO_REG_FW_RDY_BITMASK: u32 = 1 << 24; + +pub(crate) const BTSDIO_FWBUF_SIZE: u32 = 0x1000; +pub(crate) const BTSDIO_OFFSET_HOST_WRITE_BUF: u32 = 0; +pub(crate) const BTSDIO_OFFSET_HOST_READ_BUF: u32 = BTSDIO_FWBUF_SIZE; + +pub(crate) const BTSDIO_OFFSET_HOST2BT_IN: u32 = 0x00002000; +pub(crate) const BTSDIO_OFFSET_HOST2BT_OUT: u32 = 0x00002004; +pub(crate) const BTSDIO_OFFSET_BT2HOST_IN: u32 = 0x00002008; +pub(crate) const BTSDIO_OFFSET_BT2HOST_OUT: u32 = 0x0000200C; + // Security type (authentication and encryption types are combined using bit mask) #[allow(non_camel_case_types)] #[derive(Copy, Clone, PartialEq)] @@ -317,3 +365,306 @@ impl core::fmt::Display for FormatInterrupt { core::fmt::Debug::fmt(self, f) } } + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub(crate) enum Ioctl { + GetMagic = 0, + GetVersion = 1, + Up = 2, + Down = 3, + GetLoop = 4, + SetLoop = 5, + Dump = 6, + GetMsglevel = 7, + SetMsglevel = 8, + GetPromisc = 9, + SetPromisc = 10, + GetRate = 12, + GetInstance = 14, + GetInfra = 19, + SetInfra = 20, + GetAuth = 21, + SetAuth = 22, + GetBssid = 23, + SetBssid = 24, + GetSsid = 25, + SetSsid = 26, + Restart = 27, + GetChannel = 29, + SetChannel = 30, + GetSrl = 31, + SetSrl = 32, + GetLrl = 33, + SetLrl = 34, + GetPlcphdr = 35, + SetPlcphdr = 36, + GetRadio = 37, + SetRadio = 38, + GetPhytype = 39, + DumpRate = 40, + SetRateParams = 41, + GetKey = 44, + SetKey = 45, + GetRegulatory = 46, + SetRegulatory = 47, + GetPassiveScan = 48, + SetPassiveScan = 49, + Scan = 50, + ScanResults = 51, + Disassoc = 52, + Reassoc = 53, + GetRoamTrigger = 54, + SetRoamTrigger = 55, + GetRoamDelta = 56, + SetRoamDelta = 57, + GetRoamScanPeriod = 58, + SetRoamScanPeriod = 59, + Evm = 60, + GetTxant = 61, + SetTxant = 62, + GetAntdiv = 63, + SetAntdiv = 64, + GetClosed = 67, + SetClosed = 68, + GetMaclist = 69, + SetMaclist = 70, + GetRateset = 71, + SetRateset = 72, + Longtrain = 74, + GetBcnprd = 75, + SetBcnprd = 76, + GetDtimprd = 77, + SetDtimprd = 78, + GetSrom = 79, + SetSrom = 80, + GetWepRestrict = 81, + SetWepRestrict = 82, + GetCountry = 83, + SetCountry = 84, + GetPm = 85, + SetPm = 86, + GetWake = 87, + SetWake = 88, + GetForcelink = 90, + SetForcelink = 91, + FreqAccuracy = 92, + CarrierSuppress = 93, + GetPhyreg = 94, + SetPhyreg = 95, + GetRadioreg = 96, + SetRadioreg = 97, + GetRevinfo = 98, + GetUcantdiv = 99, + SetUcantdiv = 100, + RReg = 101, + WReg = 102, + GetMacmode = 105, + SetMacmode = 106, + GetMonitor = 107, + SetMonitor = 108, + GetGmode = 109, + SetGmode = 110, + GetLegacyErp = 111, + SetLegacyErp = 112, + GetRxAnt = 113, + GetCurrRateset = 114, + GetScansuppress = 115, + SetScansuppress = 116, + GetAp = 117, + SetAp = 118, + GetEapRestrict = 119, + SetEapRestrict = 120, + ScbAuthorize = 121, + ScbDeauthorize = 122, + GetWdslist = 123, + SetWdslist = 124, + GetAtim = 125, + SetAtim = 126, + GetRssi = 127, + GetPhyantdiv = 128, + SetPhyantdiv = 129, + ApRxOnly = 130, + GetTxPathPwr = 131, + SetTxPathPwr = 132, + GetWsec = 133, + SetWsec = 134, + GetPhyNoise = 135, + GetBssInfo = 136, + GetPktcnts = 137, + GetLazywds = 138, + SetLazywds = 139, + GetBandlist = 140, + GetBand = 141, + SetBand = 142, + ScbDeauthenticate = 143, + GetShortslot = 144, + GetShortslotOverride = 145, + SetShortslotOverride = 146, + GetShortslotRestrict = 147, + SetShortslotRestrict = 148, + GetGmodeProtection = 149, + GetGmodeProtectionOverride = 150, + SetGmodeProtectionOverride = 151, + Upgrade = 152, + GetIgnoreBcns = 155, + SetIgnoreBcns = 156, + GetScbTimeout = 157, + SetScbTimeout = 158, + GetAssoclist = 159, + GetClk = 160, + SetClk = 161, + GetUp = 162, + Out = 163, + GetWpaAuth = 164, + SetWpaAuth = 165, + GetUcflags = 166, + SetUcflags = 167, + GetPwridx = 168, + SetPwridx = 169, + GetTssi = 170, + GetSupRatesetOverride = 171, + SetSupRatesetOverride = 172, + GetProtectionControl = 178, + SetProtectionControl = 179, + GetPhylist = 180, + EncryptStrength = 181, + DecryptStatus = 182, + GetKeySeq = 183, + GetScanChannelTime = 184, + SetScanChannelTime = 185, + GetScanUnassocTime = 186, + SetScanUnassocTime = 187, + GetScanHomeTime = 188, + SetScanHomeTime = 189, + GetScanNprobes = 190, + SetScanNprobes = 191, + GetPrbRespTimeout = 192, + SetPrbRespTimeout = 193, + GetAtten = 194, + SetAtten = 195, + GetShmem = 196, + SetShmem = 197, + SetWsecTest = 200, + ScbDeauthenticateForReason = 201, + TkipCountermeasures = 202, + GetPiomode = 203, + SetPiomode = 204, + SetAssocPrefer = 205, + GetAssocPrefer = 206, + SetRoamPrefer = 207, + GetRoamPrefer = 208, + SetLed = 209, + GetLed = 210, + GetInterferenceMode = 211, + SetInterferenceMode = 212, + GetChannelQa = 213, + StartChannelQa = 214, + GetChannelSel = 215, + StartChannelSel = 216, + GetValidChannels = 217, + GetFakefrag = 218, + SetFakefrag = 219, + GetPwroutPercentage = 220, + SetPwroutPercentage = 221, + SetBadFramePreempt = 222, + GetBadFramePreempt = 223, + SetLeapList = 224, + GetLeapList = 225, + GetCwmin = 226, + SetCwmin = 227, + GetCwmax = 228, + SetCwmax = 229, + GetWet = 230, + SetWet = 231, + GetPub = 232, + GetKeyPrimary = 235, + SetKeyPrimary = 236, + GetAciArgs = 238, + SetAciArgs = 239, + UnsetCallback = 240, + SetCallback = 241, + GetRadar = 242, + SetRadar = 243, + SetSpectManagment = 244, + GetSpectManagment = 245, + WdsGetRemoteHwaddr = 246, + WdsGetWpaSup = 247, + SetCsScanTimer = 248, + GetCsScanTimer = 249, + MeasureRequest = 250, + Init = 251, + SendQuiet = 252, + Keepalive = 253, + SendPwrConstraint = 254, + UpgradeStatus = 255, + CurrentPwr = 256, + GetScanPassiveTime = 257, + SetScanPassiveTime = 258, + LegacyLinkBehavior = 259, + GetChannelsInCountry = 260, + GetCountryList = 261, + GetVar = 262, + SetVar = 263, + NvramGet = 264, + NvramSet = 265, + NvramDump = 266, + Reboot = 267, + SetWsecPmk = 268, + GetAuthMode = 269, + SetAuthMode = 270, + GetWakeentry = 271, + SetWakeentry = 272, + NdconfigItem = 273, + Nvotpw = 274, + Otpw = 275, + IovBlockGet = 276, + IovModulesGet = 277, + SoftReset = 278, + GetAllowMode = 279, + SetAllowMode = 280, + GetDesiredBssid = 281, + SetDesiredBssid = 282, + DisassocMyap = 283, + GetNbands = 284, + GetBandstates = 285, + GetWlcBssInfo = 286, + GetAssocInfo = 287, + GetOidPhy = 288, + SetOidPhy = 289, + SetAssocTime = 290, + GetDesiredSsid = 291, + GetChanspec = 292, + GetAssocState = 293, + SetPhyState = 294, + GetScanPending = 295, + GetScanreqPending = 296, + GetPrevRoamReason = 297, + SetPrevRoamReason = 298, + GetBandstatesPi = 299, + GetPhyState = 300, + GetBssWpaRsn = 301, + GetBssWpa2Rsn = 302, + GetBssBcnTs = 303, + GetIntDisassoc = 304, + SetNumPeers = 305, + GetNumBss = 306, + GetWsecPmk = 318, + GetRandomBytes = 319, +} + +pub(crate) const WSEC_TKIP: u32 = 0x02; +pub(crate) const WSEC_AES: u32 = 0x04; + +pub(crate) const AUTH_OPEN: u32 = 0x00; +pub(crate) const AUTH_SAE: u32 = 0x03; + +pub(crate) const MFP_NONE: u32 = 0; +pub(crate) const MFP_CAPABLE: u32 = 1; +pub(crate) const MFP_REQUIRED: u32 = 2; + +pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000; +pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004; +pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080; +pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000; diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 8944865c1..071ba88e4 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -35,16 +35,19 @@ pub struct Control<'a> { ioctl_state: &'a IoctlState, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ScanType { Active, Passive, } -#[derive(Clone)] +/// Scan options. +#[derive(Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] pub struct ScanOptions { + /// SSID to scan for. pub ssid: Option>, /// If set to `None`, all APs will be returned. If set to `Some`, only APs /// with the specified BSSID will be returned. @@ -72,6 +75,79 @@ impl Default for ScanOptions { } } +/// Authentication type, used in [`JoinOptions::auth`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum JoinAuth { + /// Open network + Open, + /// WPA only + Wpa, + /// WPA2 only + Wpa2, + /// WPA3 only + Wpa3, + /// WPA2 + WPA3 + Wpa2Wpa3, +} + +/// Options for [`Control::join`]. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct JoinOptions<'a> { + /// Authentication type. Default `Wpa2Wpa3`. + pub auth: JoinAuth, + /// Enable TKIP encryption. Default false. + pub cipher_tkip: bool, + /// Enable AES encryption. Default true. + pub cipher_aes: bool, + /// Passphrase. Default empty. + pub passphrase: &'a [u8], + /// If false, `passphrase` is the human-readable passphrase string. + /// If true, `passphrase` is the result of applying the PBKDF2 hash to the + /// passphrase string. This makes it possible to avoid storing unhashed passwords. + /// + /// This is not compatible with WPA3. + /// Default false. + pub passphrase_is_prehashed: bool, +} + +impl<'a> JoinOptions<'a> { + /// Create a new `JoinOptions` for joining open networks. + pub fn new_open() -> Self { + Self { + auth: JoinAuth::Open, + cipher_tkip: false, + cipher_aes: false, + passphrase: &[], + passphrase_is_prehashed: false, + } + } + + /// Create a new `JoinOptions` for joining encrypted networks. + /// + /// Defaults to supporting WPA2+WPA3 with AES only, you may edit + /// the returned options to change this. + pub fn new(passphrase: &'a [u8]) -> Self { + let mut this = Self::default(); + this.passphrase = passphrase; + this + } +} + +impl<'a> Default for JoinOptions<'a> { + fn default() -> Self { + Self { + auth: JoinAuth::Wpa2Wpa3, + cipher_tkip: false, + cipher_aes: true, + passphrase: &[], + passphrase_is_prehashed: false, + } + } +} + impl<'a> Control<'a> { pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { Self { @@ -81,8 +157,7 @@ impl<'a> Control<'a> { } } - /// Initialize WiFi controller. - pub async fn init(&mut self, clm: &[u8]) { + async fn load_clm(&mut self, clm: &[u8]) { const CHUNK_SIZE: usize = 1024; debug!("Downloading CLM..."); @@ -108,12 +183,17 @@ impl<'a> Control<'a> { buf[0..8].copy_from_slice(b"clmload\x00"); buf[8..20].copy_from_slice(&header.to_bytes()); buf[20..][..chunk.len()].copy_from_slice(&chunk); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()]) .await; } // check clmload ok assert_eq!(self.get_iovar_u32("clmload_status").await, 0); + } + + /// Initialize WiFi controller. + pub async fn init(&mut self, clm: &[u8]) { + self.load_clm(&clm).await; debug!("Configuring misc stuff..."); @@ -139,7 +219,7 @@ impl<'a> Control<'a> { Timer::after_millis(100).await; // Set antenna to chip antenna - self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await; self.set_iovar_u32("bus:txglom", 0).await; Timer::after_millis(100).await; @@ -177,24 +257,24 @@ impl<'a> Control<'a> { Timer::after_millis(100).await; - self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto - self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any Timer::after_millis(100).await; self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr)); - debug!("INIT DONE"); + debug!("cyw43 control init done"); } /// Set the WiFi interface up. async fn up(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await; } /// Set the interface down. async fn down(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await; } /// Set power management mode. @@ -207,17 +287,74 @@ impl<'a> Control<'a> { self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; } - self.ioctl_set_u32(86, 0, mode_num).await; + self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await; } /// Join an unprotected network with the provided ssid. - pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { + pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> { self.set_iovar_u32("ampdu_ba_wsize", 8).await; - self.ioctl_set_u32(134, 0, 0).await; // wsec = open - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) + if options.auth == JoinAuth::Open { + self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await; + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await; + } else { + let mut wsec = 0; + if options.cipher_aes { + wsec |= WSEC_AES; + } + if options.cipher_tkip { + wsec |= WSEC_TKIP; + } + self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await; + + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after_millis(100).await; + + let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth { + JoinAuth::Open => unreachable!(), + JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK), + JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK), + JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK), + JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK), + }; + + if wpa12 { + let mut flags = 0; + if !options.passphrase_is_prehashed { + flags |= 1; + } + let mut pfi = PassphraseInfo { + len: options.passphrase.len() as _, + flags, + passphrase: [0; 64], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) + .await; + } + + if wpa3 { + let mut pfi = SaePassphraseInfo { + len: options.passphrase.len() as _, + passphrase: [0; 128], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.set_iovar("sae_password", &pfi.to_bytes()).await; + } + + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await; + self.set_iovar_u32("mfp", mfp).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await; + } let mut i = SsidInfo { len: ssid.len() as _, @@ -228,69 +365,13 @@ impl<'a> Control<'a> { self.wait_for_join(i).await } - /// Join a protected network with the provided ssid and [`PassphraseInfo`]. - async fn join_wpa2_passphrase_info(&mut self, ssid: &str, passphrase_info: &PassphraseInfo) -> Result<(), Error> { - self.set_iovar_u32("ampdu_ba_wsize", 8).await; - - self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 - self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; - self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; - self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; - - Timer::after_millis(100).await; - - self.ioctl( - IoctlType::Set, - IOCTL_CMD_SET_PASSPHRASE, - 0, - &mut passphrase_info.to_bytes(), - ) - .await; // WLC_SET_WSEC_PMK - - self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 - self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) - self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth - - let mut i = SsidInfo { - len: ssid.len() as _, - ssid: [0; 32], - }; - i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); - - self.wait_for_join(i).await - } - - /// Join a protected network with the provided ssid and passphrase. - pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { - let mut pfi = PassphraseInfo { - len: passphrase.len() as _, - flags: 1, - passphrase: [0; 64], - }; - pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); - self.join_wpa2_passphrase_info(ssid, &pfi).await - } - - /// Join a protected network with the provided ssid and precomputed PSK. - pub async fn join_wpa2_psk(&mut self, ssid: &str, psk: &[u8; 32]) -> Result<(), Error> { - let mut pfi = PassphraseInfo { - len: psk.len() as _, - flags: 0, - passphrase: [0; 64], - }; - pfi.passphrase[..psk.len()].copy_from_slice(psk); - self.join_wpa2_passphrase_info(ssid, &pfi).await - } - async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); let mut subscriber = self.events.queue.subscriber().unwrap(); // the actual join operation starts here // we make sure to enable events before so we don't miss any - // set_ssid - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) - .await; + self.ioctl(IoctlType::Set, Ioctl::SetSsid, 0, &mut i.to_bytes()).await; // to complete the join, we wait for a SET_SSID event // we also save the AUTH status for the user, it may be interesting @@ -351,7 +432,7 @@ impl<'a> Control<'a> { self.up().await; // Turn on AP mode - self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await; // Set SSID let mut i = SsidInfoWithIndex { @@ -365,7 +446,7 @@ impl<'a> Control<'a> { self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; // Set channel number - self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; + self.ioctl_set_u32(Ioctl::SetChannel, 0, channel as u32).await; // Set security self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; @@ -382,7 +463,7 @@ impl<'a> Control<'a> { passphrase: [0; 64], }; pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) .await; } @@ -399,7 +480,7 @@ impl<'a> Control<'a> { self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN // Turn off AP mode - self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await; // Temporarily set wifi down self.down().await; @@ -478,11 +559,11 @@ impl<'a> Control<'a> { } async fn set_iovar(&mut self, name: &str, val: &[u8]) { - self.set_iovar_v::<64>(name, val).await + self.set_iovar_v::<196>(name, val).await } async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { - debug!("set {} = {:02x}", name, Bytes(val)); + debug!("iovar set {} = {:02x}", name, Bytes(val)); let mut buf = [0; BUFSIZE]; buf[..name.len()].copy_from_slice(name.as_bytes()); @@ -490,13 +571,13 @@ impl<'a> Control<'a> { buf[name.len() + 1..][..val.len()].copy_from_slice(val); let total_len = name.len() + 1 + val.len(); - self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) + self.ioctl_inner(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len]) .await; } // TODO this is not really working, it always returns all zeros. async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { - debug!("get {}", name); + debug!("iovar get {}", name); let mut buf = [0; 64]; buf[..name.len()].copy_from_slice(name.as_bytes()); @@ -504,7 +585,7 @@ impl<'a> Control<'a> { let total_len = max(name.len() + 1, res.len()); let res_len = self - .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) + .ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len]) .await; let out_len = min(res.len(), res_len); @@ -512,12 +593,20 @@ impl<'a> Control<'a> { out_len } - async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { + async fn ioctl_set_u32(&mut self, cmd: Ioctl, iface: u32, val: u32) { let mut buf = val.to_le_bytes(); self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; } - async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + if kind == IoctlType::Set { + debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf)); + } + let n = self.ioctl_inner(kind, cmd, iface, buf).await; + n + } + + async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { struct CancelOnDrop<'a>(&'a IoctlState); impl CancelOnDrop<'_> { @@ -609,7 +698,7 @@ impl<'a> Control<'a> { } /// Leave the wifi, with which we are currently associated. pub async fn leave(&mut self) { - self.ioctl(IoctlType::Set, IOCTL_CMD_DISASSOC, 0, &mut []).await; + self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await; info!("Disassociated") } diff --git a/cyw43/src/ioctl.rs b/cyw43/src/ioctl.rs index 61524c274..f8b2d9aba 100644 --- a/cyw43/src/ioctl.rs +++ b/cyw43/src/ioctl.rs @@ -4,9 +4,10 @@ use core::task::{Poll, Waker}; use embassy_sync::waitqueue::WakerRegistration; +use crate::consts::Ioctl; use crate::fmt::Bytes; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq)] pub enum IoctlType { Get = 0, Set = 2, @@ -16,7 +17,7 @@ pub enum IoctlType { pub struct PendingIoctl { pub buf: *mut [u8], pub kind: IoctlType, - pub cmd: u32, + pub cmd: Ioctl, pub iface: u32, } @@ -101,7 +102,7 @@ impl IoctlState { self.state.set(IoctlStateInner::Done { resp_len: 0 }); } - pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { self.state .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); self.wake_runner(); diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs index 19b0cb194..6b71c18e6 100644 --- a/cyw43/src/lib.rs +++ b/cyw43/src/lib.rs @@ -8,18 +8,18 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +#[cfg(feature = "bluetooth")] +mod bluetooth; mod bus; mod consts; +mod control; mod countries; mod events; mod ioctl; -mod structs; - -mod control; mod nvram; mod runner; - -use core::slice; +mod structs; +mod util; use embassy_net_driver_channel as ch; use embedded_hal_1::digital::OutputPin; @@ -28,7 +28,9 @@ use ioctl::IoctlState; use crate::bus::Bus; pub use crate::bus::SpiBusCyw43; -pub use crate::control::{AddMulticastAddressError, Control, Error as ControlError, Scanner}; +pub use crate::control::{ + AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, Scanner, +}; pub use crate::runner::Runner; pub use crate::structs::BssInfo; @@ -56,6 +58,7 @@ impl Core { struct Chip { arm_core_base_address: u32, socsram_base_address: u32, + bluetooth_base_address: u32, socsram_wrapper_base_address: u32, sdiod_core_base_address: u32, pmu_base_address: u32, @@ -83,6 +86,7 @@ const WRAPPER_REGISTER_OFFSET: u32 = 0x100000; const CHIP: Chip = Chip { arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET, socsram_base_address: 0x18004000, + bluetooth_base_address: 0x19000000, socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET, sdiod_core_base_address: 0x18002000, pmu_base_address: 0x18000000, @@ -107,6 +111,12 @@ const CHIP: Chip = Chip { /// Driver state. pub struct State { ioctl_state: IoctlState, + net: NetState, + #[cfg(feature = "bluetooth")] + bt: bluetooth::BtState, +} + +struct NetState { ch: ch::State, events: Events, } @@ -116,8 +126,12 @@ impl State { pub fn new() -> Self { Self { ioctl_state: IoctlState::new(), - ch: ch::State::new(), - events: Events::new(), + net: NetState { + ch: ch::State::new(), + events: Events::new(), + }, + #[cfg(feature = "bluetooth")] + bt: bluetooth::BtState::new(), } } } @@ -225,21 +239,60 @@ where PWR: OutputPin, SPI: SpiBusCyw43, { - let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); + let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); let state_ch = ch_runner.state_runner(); - let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); + let mut runner = Runner::new( + ch_runner, + Bus::new(pwr, spi), + &state.ioctl_state, + &state.net.events, + #[cfg(feature = "bluetooth")] + None, + ); - runner.init(firmware).await; + runner.init(firmware, None).await; + let control = Control::new(state_ch, &state.net.events, &state.ioctl_state); - ( - device, - Control::new(state_ch, &state.events, &state.ioctl_state), - runner, - ) + (device, control, runner) } -fn slice8_mut(x: &mut [u32]) -> &mut [u8] { - let len = x.len() * 4; - unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +/// Create a new instance of the CYW43 driver. +/// +/// Returns a handle to the network device, control handle and a runner for driving the low level +/// stack. +#[cfg(feature = "bluetooth")] +pub async fn new_with_bluetooth<'a, PWR, SPI>( + state: &'a mut State, + pwr: PWR, + spi: SPI, + wifi_firmware: &[u8], + bluetooth_firmware: &[u8], +) -> ( + NetDriver<'a>, + bluetooth::BtDriver<'a>, + Control<'a>, + Runner<'a, PWR, SPI>, +) +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); + let state_ch = ch_runner.state_runner(); + + let (bt_runner, bt_driver) = bluetooth::new(&mut state.bt); + let mut runner = Runner::new( + ch_runner, + Bus::new(pwr, spi), + &state.ioctl_state, + &state.net.events, + #[cfg(feature = "bluetooth")] + Some(bt_runner), + ); + + runner.init(wifi_firmware, Some(bluetooth_firmware)).await; + let control = Control::new(state_ch, &state.net.events, &state.ioctl_state); + + (device, bt_driver, control, runner) } diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs index e90316302..77910b281 100644 --- a/cyw43/src/runner.rs +++ b/cyw43/src/runner.rs @@ -1,4 +1,4 @@ -use embassy_futures::select::{select3, Either3}; +use embassy_futures::select::{select4, Either4}; use embassy_net_driver_channel as ch; use embassy_time::{block_for, Duration, Timer}; use embedded_hal_1::digital::OutputPin; @@ -11,7 +11,8 @@ use crate::fmt::Bytes; use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; use crate::nvram::NVRAM; use crate::structs::*; -use crate::{events, slice8_mut, Core, CHIP, MTU}; +use crate::util::slice8_mut; +use crate::{events, Core, CHIP, MTU}; #[cfg(feature = "firmware-logs")] struct LogState { @@ -36,7 +37,7 @@ impl Default for LogState { /// Driver communicating with the WiFi chip. pub struct Runner<'a, PWR, SPI> { ch: ch::Runner<'a, MTU>, - bus: Bus, + pub(crate) bus: Bus, ioctl_state: &'a IoctlState, ioctl_id: u16, @@ -47,6 +48,9 @@ pub struct Runner<'a, PWR, SPI> { #[cfg(feature = "firmware-logs")] log: LogState, + + #[cfg(feature = "bluetooth")] + pub(crate) bt: Option>, } impl<'a, PWR, SPI> Runner<'a, PWR, SPI> @@ -59,6 +63,7 @@ where bus: Bus, ioctl_state: &'a IoctlState, events: &'a Events, + #[cfg(feature = "bluetooth")] bt: Option>, ) -> Self { Self { ch, @@ -70,33 +75,52 @@ where events, #[cfg(feature = "firmware-logs")] log: LogState::default(), + #[cfg(feature = "bluetooth")] + bt, } } - pub(crate) async fn init(&mut self, firmware: &[u8]) { - self.bus.init().await; + pub(crate) async fn init(&mut self, wifi_fw: &[u8], bt_fw: Option<&[u8]>) { + self.bus.init(bt_fw.is_some()).await; // Init ALP (Active Low Power) clock + debug!("init alp"); self.bus .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) .await; + + debug!("set f2 watermark"); + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 0x10) + .await; + let watermark = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK).await; + debug!("watermark = {:02x}", watermark); + assert!(watermark == 0x10); + debug!("waiting for clock..."); while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} debug!("clock ok"); + // clear request for ALP + debug!("clear request for ALP"); + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0).await; + let chip_id = self.bus.bp_read16(0x1800_0000).await; debug!("chip ID: {}", chip_id); // Upload firmware. self.core_disable(Core::WLAN).await; + self.core_disable(Core::SOCSRAM).await; // TODO: is this needed if we reset right after? self.core_reset(Core::SOCSRAM).await; + + // this is 4343x specific stuff: Disable remap for SRAM_3 self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; let ram_addr = CHIP.atcm_ram_base_address; debug!("loading fw"); - self.bus.bp_write(ram_addr, firmware).await; + self.bus.bp_write(ram_addr, wifi_fw).await; debug!("loading nvram"); // Round up to 4 bytes. @@ -116,10 +140,23 @@ where self.core_reset(Core::WLAN).await; assert!(self.core_is_up(Core::WLAN).await); + // wait until HT clock is available; takes about 29ms + debug!("wait for HT clock"); while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} // "Set up the interrupt mask and enable interrupts" - // self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; + debug!("setup interrupt mask"); + self.bus + .bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_SW_MASK) + .await; + + // Set up the interrupt mask and enable interrupts + if bt_fw.is_some() { + debug!("bluetooth setup interrupt mask"); + self.bus + .bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_FC_CHANGE) + .await; + } self.bus .write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE) @@ -128,11 +165,11 @@ where // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." // Sounds scary... self.bus - .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32) + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, SPI_F2_WATERMARK) .await; - // wait for wifi startup - debug!("waiting for wifi init..."); + // wait for F2 to be ready + debug!("waiting for F2 to be ready..."); while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} // Some random configs related to sleep. @@ -153,19 +190,27 @@ where */ // clear pulls + debug!("clear pad pulls"); self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; // start HT clock - //self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; - //debug!("waiting for HT clock..."); - //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} - //debug!("clock ok"); + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10) + .await; // SBSDIO_HT_AVAIL_REQ + debug!("waiting for HT clock..."); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + debug!("clock ok"); #[cfg(feature = "firmware-logs")] self.log_init().await; - debug!("wifi init done"); + #[cfg(feature = "bluetooth")] + if let Some(bt_fw) = bt_fw { + self.bt.as_mut().unwrap().init_bluetooth(&mut self.bus, bt_fw).await; + } + + debug!("cyw43 runner init done"); } #[cfg(feature = "firmware-logs")] @@ -222,7 +267,7 @@ where } } - /// Run the + /// Run the CYW43 event handling loop. pub async fn run(mut self) -> ! { let mut buf = [0; 512]; loop { @@ -231,11 +276,27 @@ where if self.has_credit() { let ioctl = self.ioctl_state.wait_pending(); - let tx = self.ch.tx_buf(); + let wifi_tx = self.ch.tx_buf(); + #[cfg(feature = "bluetooth")] + let bt_tx = async { + match &mut self.bt { + Some(bt) => bt.tx_chan.receive().await, + None => core::future::pending().await, + } + }; + #[cfg(not(feature = "bluetooth"))] + let bt_tx = core::future::pending::<()>(); + + // interrupts aren't working yet for bluetooth. Do busy-polling instead. + // Note for this to work `ev` has to go last in the `select()`. It prefers + // first futures if they're ready, so other select branches don't get starved.` + #[cfg(feature = "bluetooth")] + let ev = core::future::ready(()); + #[cfg(not(feature = "bluetooth"))] let ev = self.bus.wait_for_event(); - match select3(ioctl, tx, ev).await { - Either3::First(PendingIoctl { + match select4(ioctl, wifi_tx, bt_tx, ev).await { + Either4::First(PendingIoctl { buf: iobuf, kind, cmd, @@ -244,7 +305,7 @@ where self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }, &mut buf).await; self.check_status(&mut buf).await; } - Either3::Second(packet) => { + Either4::Second(packet) => { trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); let buf8 = slice8_mut(&mut buf); @@ -298,8 +359,19 @@ where self.ch.tx_done(); self.check_status(&mut buf).await; } - Either3::Third(()) => { + Either4::Third(_) => { + #[cfg(feature = "bluetooth")] + self.bt.as_mut().unwrap().hci_write(&mut self.bus).await; + } + Either4::Fourth(()) => { self.handle_irq(&mut buf).await; + + // If we do busy-polling, make sure to yield. + // `handle_irq` will only do a 32bit read if there's no work to do, which is really fast. + // Depending on optimization level, it is possible that the 32-bit read finishes on + // first poll, so it never yields and we starve all other tasks. + #[cfg(feature = "bluetooth")] + embassy_futures::yield_now().await; } } } else { @@ -314,17 +386,24 @@ where async fn handle_irq(&mut self, buf: &mut [u32; 512]) { // Receive stuff let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; - trace!("irq{}", FormatInterrupt(irq)); + if irq != 0 { + trace!("irq{}", FormatInterrupt(irq)); + } if irq & IRQ_F2_PACKET_AVAILABLE != 0 { self.check_status(buf).await; } if irq & IRQ_DATA_UNAVAILABLE != 0 { - // TODO what should we do here? - warn!("IRQ DATA_UNAVAILABLE, clearing..."); + // this seems to be ignorable with no ill effects. + trace!("IRQ DATA_UNAVAILABLE, clearing..."); self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; } + + #[cfg(feature = "bluetooth")] + if let Some(bt) = &mut self.bt { + bt.handle_irq(&mut self.bus).await; + } } /// Handle F2 events while status register is set @@ -481,7 +560,7 @@ where self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 } - async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8], buf: &mut [u32; 512]) { + async fn send_ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) { let buf8 = slice8_mut(buf); let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); @@ -503,7 +582,7 @@ where }; let cdc_header = CdcHeader { - cmd: cmd, + cmd: cmd as u32, len: data.len() as _, flags: kind as u16 | (iface as u16) << 12, id: self.ioctl_id, diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs index ae7ef6038..81ae6a98d 100644 --- a/cyw43/src/structs.rs +++ b/cyw43/src/structs.rs @@ -394,6 +394,15 @@ pub struct PassphraseInfo { } impl_bytes!(PassphraseInfo); +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SaePassphraseInfo { + pub len: u16, + pub passphrase: [u8; 128], +} +impl_bytes!(SaePassphraseInfo); + #[derive(Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(C)] diff --git a/cyw43/src/util.rs b/cyw43/src/util.rs new file mode 100644 index 000000000..a4adbd4ed --- /dev/null +++ b/cyw43/src/util.rs @@ -0,0 +1,20 @@ +#![allow(unused)] + +use core::slice; + +pub(crate) fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} + +pub(crate) fn is_aligned(a: u32, x: u32) -> bool { + (a & (x - 1)) == 0 +} + +pub(crate) fn round_down(x: u32, a: u32) -> u32 { + x & !(a - 1) +} + +pub(crate) fn round_up(x: u32, a: u32) -> u32 { + ((x + a - 1) / a) * a +} diff --git a/docs/examples/basic/Cargo.toml b/docs/examples/basic/Cargo.toml index e82165032..5d391adf3 100644 --- a/docs/examples/basic/Cargo.toml +++ b/docs/examples/basic/Cargo.toml @@ -6,9 +6,9 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.5.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.3.1", path = "../../../embassy-time", features = ["defmt"] } -embassy-nrf = { version = "0.1.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } +embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["defmt", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote"] } defmt = "0.3" defmt-rtt = "0.3" diff --git a/docs/examples/layer-by-layer/blinky-async/Cargo.toml b/docs/examples/layer-by-layer/blinky-async/Cargo.toml index 64f7e8403..7f8d8af3e 100644 --- a/docs/examples/layer-by-layer/blinky-async/Cargo.toml +++ b/docs/examples/layer-by-layer/blinky-async/Cargo.toml @@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0" cortex-m = "0.7" cortex-m-rt = "0.7" embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] } -embassy-executor = { version = "0.5.0", features = ["arch-cortex-m", "executor-thread"] } +embassy-executor = { version = "0.6.0", features = ["arch-cortex-m", "executor-thread"] } defmt = "0.3.0" defmt-rtt = "0.3.0" diff --git a/docs/pages/basic_application.adoc b/docs/pages/basic_application.adoc index 7b4ebda4f..5c4e3e8b3 100644 --- a/docs/pages/basic_application.adoc +++ b/docs/pages/basic_application.adoc @@ -4,7 +4,7 @@ So you've got one of the examples running, but what now? Let's go through a simp == Main -The full example can be found link:https://github.com/embassy-rs/embassy/tree/master/docs/examples/basic[here]. +The full example can be found link:https://github.com/embassy-rs/embassy/tree/main/docs/examples/basic[here]. NOTE: If you’re using VS Code and rust-analyzer to view and edit the examples, you may need to make some changes to `.vscode/settings.json` to tell it which project we’re working on. Follow the instructions commented in that file to get rust-analyzer working correctly. diff --git a/docs/pages/bootloader.adoc b/docs/pages/bootloader.adoc index 3b0cdb182..d8d50040b 100644 --- a/docs/pages/bootloader.adoc +++ b/docs/pages/bootloader.adoc @@ -19,6 +19,8 @@ The bootloader supports In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work. +STM32L0x1 devices require the `flash-erase-zero` feature to be enabled. + == Design image::bootloader_flash.png[Bootloader flash layout] diff --git a/docs/pages/embassy_in_the_wild.adoc b/docs/pages/embassy_in_the_wild.adoc index 76b1169bd..bb457a869 100644 --- a/docs/pages/embassy_in_the_wild.adoc +++ b/docs/pages/embassy_in_the_wild.adoc @@ -2,6 +2,8 @@ Here are known examples of real-world projects which make use of Embassy. Feel free to link:https://github.com/embassy-rs/embassy/blob/main/docs/pages/embassy_in_the_wild.adoc[add more]! +* link:https://github.com/1-rafael-1/pi-pico-alarmclock-rust[A Raspberry Pi Pico W Alarmclock] +** A hobbyist project building an alarm clock around a Pi Pico W complete with code, components list and enclosure design files. * link:https://github.com/haobogu/rmk/[RMK: A feature-rich Rust keyboard firmware] ** RMK has built-in layer support, wireless(BLE) support, real-time key editing support using vial, and more! ** Targets STM32, RP2040, nRF52 and ESP32 MCUs diff --git a/docs/pages/faq.adoc b/docs/pages/faq.adoc index 4ab04e2a1..8eb947b5e 100644 --- a/docs/pages/faq.adoc +++ b/docs/pages/faq.adoc @@ -361,4 +361,14 @@ Practically, there's not a LOT of difference either way - so go with what makes == splitting peripherals resources between tasks -There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example] \ No newline at end of file +There are two ways to split resources between tasks, either manually assigned or by a convenient macro. See link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[this example] + +== My code/driver works in debug mode, but not release mode (or with LTO) + +Issues like these while implementing drivers often fall into one of the following general causes, which are a good list of common errors to check for: + +1. Some kind of race condition - the faster code means you miss an interrupt or something +2. Some kind of UB, if you have unsafe code, or something like DMA with fences missing +3. Some kind of hardware errata, or some hardware misconfiguration like wrong clock speeds +4. Some issue with an interrupt handler, either enabling, disabling, or re-enabling of interrupts when necessary +5. Some kind of async issue, like not registering wakers fully before checking flags, or not registering or pending wakers at the right time diff --git a/docs/pages/new_project.adoc b/docs/pages/new_project.adoc index 346d9f0f8..821bcbd27 100644 --- a/docs/pages/new_project.adoc +++ b/docs/pages/new_project.adoc @@ -1,6 +1,6 @@ = Starting a new project -Once you’ve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project. +Once you’ve successfully xref:#_getting_started[run some example projects], the next step is to make a standalone Embassy project. == Tools for generating Embassy projects diff --git a/docs/pages/nrf.adoc b/docs/pages/nrf.adoc index 1706087ae..de052b63f 100644 --- a/docs/pages/nrf.adoc +++ b/docs/pages/nrf.adoc @@ -1,6 +1,6 @@ = Embassy nRF HAL -The link:https://github.com/embassy-rs/embassy/tree/master/embassy-nrf[Embassy nRF HAL] is based on the PACs (Peripheral Access Crate) from link:https://github.com/nrf-rs/[nrf-rs]. +The link:https://github.com/embassy-rs/embassy/tree/main/embassy-nrf[Embassy nRF HAL] is based on the PACs (Peripheral Access Crate) from link:https://github.com/nrf-rs/[nrf-rs]. == Timer driver diff --git a/docs/pages/overview.adoc b/docs/pages/overview.adoc index 1b9381bfe..2ebc85f6d 100644 --- a/docs/pages/overview.adoc +++ b/docs/pages/overview.adoc @@ -48,7 +48,7 @@ link:https://github.com/lora-rs/lora-rs[lora-rs] supports LoRa networking on a w link:https://docs.embassy.dev/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 -link:https://github.com/embassy-rs/embassy/tree/master/embassy-boot[embassy-boot] is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. +link:https://github.com/embassy-rs/embassy/tree/main/embassy-boot[embassy-boot] is a lightweight bootloader supporting firmware application upgrades in a power-fail-safe way, with trial boots and rollbacks. == What is DMA? @@ -77,3 +77,7 @@ For more reading material on async Rust and Embassy: * link:https://tweedegolf.nl/en/blog/65/async-rust-vs-rtos-showdown[Comparsion of FreeRTOS and Embassy] * link:https://dev.to/apollolabsbin/series/20707[Tutorials] * link:https://blog.drogue.io/firmware-updates-part-1/[Firmware Updates with Embassy] + +Videos: + +* link:https://www.youtube.com/watch?v=wni5h5vIPhU[From Zero to Async in Embedded Rust] \ No newline at end of file diff --git a/docs/pages/sharing_peripherals.adoc b/docs/pages/sharing_peripherals.adoc index 6ba13f93b..dfb8c1ffe 100644 --- a/docs/pages/sharing_peripherals.adoc +++ b/docs/pages/sharing_peripherals.adoc @@ -8,7 +8,7 @@ The following examples shows different ways to use the on-board LED on a Raspber Using mutual exclusion is the simplest way to share a peripheral. -TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here]. +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. [,rust] ---- use defmt::*; @@ -78,7 +78,7 @@ To indicate that the pin will be set to an Output. The `AnyPin` could have been A channel is another way to ensure exclusive access to a resource. Using a channel is great in the cases where the access can happen at a later point in time, allowing you to enqueue operations and do other things. -TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here]. +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. [,rust] ---- use defmt::*; diff --git a/docs/pages/stm32.adoc b/docs/pages/stm32.adoc index 7bfc0592b..df139a420 100644 --- a/docs/pages/stm32.adoc +++ b/docs/pages/stm32.adoc @@ -1,6 +1,6 @@ = Embassy STM32 HAL -The link:https://github.com/embassy-rs/embassy/tree/master/embassy-stm32[Embassy STM32 HAL] is based on the `stm32-metapac` project. +The link:https://github.com/embassy-rs/embassy/tree/main/embassy-stm32[Embassy STM32 HAL] is based on the `stm32-metapac` project. == The infinite variant problem diff --git a/docs/pages/time_keeping.adoc b/docs/pages/time_keeping.adoc index 17492a884..11ddb2b2b 100644 --- a/docs/pages/time_keeping.adoc +++ b/docs/pages/time_keeping.adoc @@ -16,7 +16,7 @@ The `embassy::time::Timer` type provides two timing methods. An example of a delay is provided as follows: -TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here]. +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. [,rust] ---- use embassy::executor::{task, Executor}; @@ -41,7 +41,7 @@ that expect a generic delay implementation to be provided. An example of how this can be used: -TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here]. +TIP: Dependencies needed to run this example link:#_the_cargo_toml[can be found here]. [,rust] ---- use embassy::executor::{task, Executor}; diff --git a/embassy-boot-nrf/Cargo.toml b/embassy-boot-nrf/Cargo.toml index 86bfc21f4..27407ae92 100644 --- a/embassy-boot-nrf/Cargo.toml +++ b/embassy-boot-nrf/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-nrf" -version = "0.2.0" +version = "0.3.0" description = "Bootloader lib for nRF chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -25,8 +25,8 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.17", optional = true } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-nrf = { version = "0.1.0", path = "../embassy-nrf", default-features = false } -embassy-boot = { version = "0.2.0", path = "../embassy-boot" } +embassy-nrf = { version = "0.2.0", path = "../embassy-nrf", default-features = false } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" diff --git a/embassy-boot-rp/Cargo.toml b/embassy-boot-rp/Cargo.toml index 23a8bb549..bf5f34abe 100644 --- a/embassy-boot-rp/Cargo.toml +++ b/embassy-boot-rp/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot-rp" -version = "0.2.0" +version = "0.3.0" description = "Bootloader lib for RP2040 chips" license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -16,6 +16,7 @@ categories = [ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot-rp/src/" target = "thumbv6m-none-eabi" +features = ["embassy-rp/rp2040"] [lib] @@ -24,9 +25,9 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4", optional = true } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-rp = { version = "0.1.0", path = "../embassy-rp", default-features = false } -embassy-boot = { version = "0.2.0", path = "../embassy-boot" } -embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-rp = { version = "0.2.0", path = "../embassy-rp", default-features = false } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } diff --git a/embassy-boot-stm32/Cargo.toml b/embassy-boot-stm32/Cargo.toml index 52ad1b463..e4ef9a612 100644 --- a/embassy-boot-stm32/Cargo.toml +++ b/embassy-boot-stm32/Cargo.toml @@ -26,7 +26,7 @@ log = { version = "0.4", optional = true } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false } -embassy-boot = { version = "0.2.0", path = "../embassy-boot" } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" diff --git a/embassy-boot/Cargo.toml b/embassy-boot/Cargo.toml index 16dc52bcc..79ba6456a 100644 --- a/embassy-boot/Cargo.toml +++ b/embassy-boot/Cargo.toml @@ -1,7 +1,7 @@ [package] edition = "2021" name = "embassy-boot" -version = "0.2.0" +version = "0.3.0" description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." license = "MIT OR Apache-2.0" repository = "https://github.com/embassy-rs/embassy" @@ -27,8 +27,8 @@ features = ["defmt"] defmt = { version = "0.3", optional = true } digest = "0.10" log = { version = "0.4", optional = true } -ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true } -embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } +ed25519-dalek = { version = "2", default-features = false, features = ["digest"], optional = true } +embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } embedded-storage = "0.3.1" embedded-storage-async = { version = "0.4.1" } @@ -42,11 +42,12 @@ rand = "0.8" futures = { version = "0.3", features = ["executor"] } sha1 = "0.10.5" critical-section = { version = "1.1.1", features = ["std"] } -ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] } +ed25519-dalek = { version = "2", default-features = false, features = ["std", "rand_core", "digest"] } [features] ed25519-dalek = ["dep:ed25519-dalek", "_verify"] ed25519-salty = ["dep:salty", "_verify"] +flash-erase-zero = [] #Internal features _verify = [] diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs index 61d61b96e..5bffdc5ea 100644 --- a/embassy-boot/src/boot_loader.rs +++ b/embassy-boot/src/boot_loader.rs @@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; +use crate::{State, DFU_DETACH_MAGIC, REVERT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -276,7 +276,7 @@ impl BootLoader BootLoader FirmwareUpdater<'d, DFU, STATE> { let mut message = [0; 64]; self.hash::(_update_len, &mut chunk_buf, &mut message).await?; - public_key.verify(&message, &signature).map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated().await; } #[cfg(feature = "ed25519-salty")] { @@ -134,10 +135,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { message, r.is_ok() ); - r.map_err(into_signature_error)? + r.map_err(into_signature_error)?; + return self.state.mark_updated().await; + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) } - - self.state.mark_updated().await } /// Verify the update in DFU with any digest. @@ -285,7 +289,8 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state().await? == State::Boot { + let state = self.get_state().await?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) @@ -299,12 +304,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { /// `mark_booted`. pub async fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned).await?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned)) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index 35772a856..08062b0d0 100644 --- a/embassy-boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -142,7 +142,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> let mut chunk_buf = [0; 2]; self.hash::(_update_len, &mut chunk_buf, &mut message)?; - public_key.verify(&message, &signature).map_err(into_signature_error)? + public_key.verify(&message, &signature).map_err(into_signature_error)?; + return self.state.mark_updated(); } #[cfg(feature = "ed25519-salty")] { @@ -169,10 +170,13 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> message, r.is_ok() ); - r.map_err(into_signature_error)? + r.map_err(into_signature_error)?; + return self.state.mark_updated(); + } + #[cfg(not(any(feature = "ed25519-dalek", feature = "ed25519-salty")))] + { + Err(FirmwareUpdaterError::Signature(signature::Error::new())) } - - self.state.mark_updated() } /// Verify the update in DFU with any digest. @@ -320,7 +324,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { + let state = self.get_state()?; + if state == State::Boot || state == State::DfuDetach || state == State::Revert { Ok(()) } else { Err(FirmwareUpdaterError::BadState) @@ -334,14 +339,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { /// `mark_booted`. pub fn get_state(&mut self) -> Result { self.state.read(0, &mut self.aligned)?; - - if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { - Ok(State::Swap) - } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { - Ok(State::DfuDetach) - } else { - Ok(State::Boot) - } + Ok(State::from(&self.aligned)) } /// Mark to trigger firmware swap on next boot. diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs index b4f03e01e..e2c4cf771 100644 --- a/embassy-boot/src/lib.rs +++ b/embassy-boot/src/lib.rs @@ -14,13 +14,18 @@ mod test_flash; // The expected value of the flash after an erase // TODO: Use the value provided by NorFlash when available +#[cfg(not(feature = "flash-erase-zero"))] pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; +#[cfg(feature = "flash-erase-zero")] +pub(crate) const STATE_ERASE_VALUE: u8 = 0x00; + pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; pub use firmware_updater::{ BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError, }; +pub(crate) const REVERT_MAGIC: u8 = 0xC0; pub(crate) const BOOT_MAGIC: u8 = 0xD0; pub(crate) const SWAP_MAGIC: u8 = 0xF0; pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; @@ -33,10 +38,30 @@ pub enum State { Boot, /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, + /// Bootloader has reverted the active partition with the dfu partition and will attempt boot. + Revert, /// Application has received a request to reboot into DFU mode to apply an update. DfuDetach, } +impl From for State +where + T: AsRef<[u8]>, +{ + fn from(magic: T) -> State { + let magic = magic.as_ref(); + if !magic.iter().any(|&b| b != SWAP_MAGIC) { + State::Swap + } else if !magic.iter().any(|&b| b != REVERT_MAGIC) { + State::Revert + } else if !magic.iter().any(|&b| b != DFU_DETACH_MAGIC) { + State::DfuDetach + } else { + State::Boot + } + } +} + /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. #[repr(align(32))] pub struct AlignedBuffer(pub [u8; N]); @@ -153,6 +178,9 @@ mod tests { // Running again should cause a revert assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + // Next time we know it was reverted + assert_eq!(State::Revert, bootloader.prepare_boot(&mut page).unwrap()); + let mut read_buf = [0; FIRMWARE_SIZE]; flash.active().read(0, &mut read_buf).unwrap(); assert_eq!(ORIGINAL, read_buf); diff --git a/embassy-embedded-hal/CHANGELOG.md b/embassy-embedded-hal/CHANGELOG.md new file mode 100644 index 000000000..f8e272160 --- /dev/null +++ b/embassy-embedded-hal/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog for embassy-embedded-hal + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Add Clone derive to flash Partition in embassy-embedded-hal +- Add support for all word sizes to async shared spi +- Add Copy and 'static constraint to Word type in SPI structs +- Improve flexibility by introducing SPI word size as a generic parameter +- Allow changing Spi/I2cDeviceWithConfig's config at runtime +- Impl `MultiwriteNorFlash` for `BlockingAsync` + +## 0.1.0 - 2024-01-10 + +- First release diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index 905439fe7..345dc3420 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-embedded-hal" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy." @@ -29,7 +29,7 @@ default = ["time"] [dependencies] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.1", path = "../embassy-time", optional = true } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ "unproven", ] } diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml index 2953e7ccc..218e820ce 100644 --- a/embassy-executor-macros/Cargo.toml +++ b/embassy-executor-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor-macros" -version = "0.4.1" +version = "0.5.0" edition = "2021" license = "MIT OR Apache-2.0" description = "macros for creating the entry point and tasks for embassy-executor" diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 77c64fd8e..5582b56ec 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.6.0 - 2024-08-05 + +- Add collapse_debuginfo to fmt.rs macros. +- initial support for avr +- use nightly waker_getters APIs + ## 0.5.0 - 2024-01-11 - Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher. diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 431165cee..01fa28b88 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-executor" -version = "0.5.0" +version = "0.6.0" edition = "2021" license = "MIT OR Apache-2.0" description = "async/await executor designed for embedded usage" @@ -33,7 +33,7 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } rtos-trace = { version = "0.1.2", optional = true } -embassy-executor-macros = { version = "0.4.0", path = "../embassy-executor-macros" } +embassy-executor-macros = { version = "0.5.0", path = "../embassy-executor-macros" } embassy-time-driver = { version = "0.1.0", path = "../embassy-time-driver", optional = true } embassy-time-queue-driver = { version = "0.1.0", path = "../embassy-time-queue-driver", optional = true } critical-section = "1.1" @@ -79,7 +79,7 @@ arch-cortex-m = ["_arch", "dep:cortex-m"] ## RISC-V 32 arch-riscv32 = ["_arch"] ## WASM -arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"] ## AVR arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] diff --git a/embassy-executor/build_common.rs b/embassy-executor/build_common.rs index 0487eb3c5..4f24e6d37 100644 --- a/embassy-executor/build_common.rs +++ b/embassy-executor/build_common.rs @@ -8,8 +8,6 @@ use std::collections::HashSet; use std::env; -use std::ffi::OsString; -use std::process::Command; /// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring /// them (`cargo:rust-check-cfg=cfg(X)`). @@ -17,7 +15,6 @@ use std::process::Command; pub struct CfgSet { enabled: HashSet, declared: HashSet, - emit_declared: bool, } impl CfgSet { @@ -25,7 +22,6 @@ impl CfgSet { Self { enabled: HashSet::new(), declared: HashSet::new(), - emit_declared: is_rustc_nightly(), } } @@ -49,7 +45,7 @@ impl CfgSet { /// /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. pub fn declare(&mut self, cfg: impl AsRef) { - if self.declared.insert(cfg.as_ref().to_owned()) && self.emit_declared { + if self.declared.insert(cfg.as_ref().to_owned()) { println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); } } @@ -69,21 +65,6 @@ impl CfgSet { } } -fn is_rustc_nightly() -> bool { - if env::var_os("EMBASSY_FORCE_CHECK_CFG").is_some() { - return true; - } - - let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); - - let output = Command::new(rustc) - .arg("--version") - .output() - .expect("failed to run `rustc --version`"); - - String::from_utf8_lossy(&output.stdout).contains("nightly") -} - /// Sets configs that describe the target platform. pub fn set_target_cfgs(cfgs: &mut CfgSet) { let target = env::var("TARGET").unwrap(); diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 553ed76d3..6a2e493a2 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -1,5 +1,4 @@ #![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(waker_getters))] #![allow(clippy::new_without_default)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs index 8d3910a25..8bb2cfd05 100644 --- a/embassy-executor/src/raw/waker.rs +++ b/embassy-executor/src/raw/waker.rs @@ -50,8 +50,7 @@ pub fn task_from_waker(waker: &Waker) -> TaskRef { #[cfg(feature = "nightly")] { - let raw_waker = waker.as_raw(); - (raw_waker.vtable(), raw_waker.data()) + (waker.vtable(), waker.data()) } }; diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 97a81a86d..57f0cb41f 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -237,7 +237,7 @@ impl Future for SelectArray { #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct SelectSlice<'a, Fut> { - inner: &'a mut [Fut], + inner: Pin<&'a mut [Fut]>, } /// Creates a new future which will select over a slice of futures. @@ -247,31 +247,26 @@ pub struct SelectSlice<'a, Fut> { /// future that was ready. /// /// If the slice is empty, the resulting future will be Pending forever. -pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> { +pub fn select_slice<'a, Fut: Future>(slice: Pin<&'a mut [Fut]>) -> SelectSlice<'a, Fut> { SelectSlice { inner: slice } } impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> { type Output = (Fut::Output, usize); - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move, - // its elements also cannot move. Therefore it is safe to access `inner` and pin - // references to the contained futures. - let item = unsafe { - self.get_unchecked_mut() - .inner - .iter_mut() - .enumerate() - .find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) { - Poll::Pending => None, - Poll::Ready(e) => Some((i, e)), - }) - }; - - match item { - Some((idx, res)) => Poll::Ready((res, idx)), - None => Poll::Pending, + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: refer to + // https://users.rust-lang.org/t/working-with-pinned-slices-are-there-any-structurally-pinning-vec-like-collection-types/50634/2 + #[inline(always)] + fn pin_iter(slice: Pin<&mut [T]>) -> impl Iterator> { + unsafe { slice.get_unchecked_mut().iter_mut().map(|v| Pin::new_unchecked(v)) } } + for (i, fut) in pin_iter(self.inner.as_mut()).enumerate() { + if let Poll::Ready(res) = fut.poll(cx) { + return Poll::Ready((res, i)); + } + } + + Poll::Pending } } diff --git a/embassy-hal-internal/Cargo.toml b/embassy-hal-internal/Cargo.toml index c5013f365..d5ca95ac2 100644 --- a/embassy-hal-internal/Cargo.toml +++ b/embassy-hal-internal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-hal-internal" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY." diff --git a/embassy-hal-internal/build_common.rs b/embassy-hal-internal/build_common.rs index 0487eb3c5..4f24e6d37 100644 --- a/embassy-hal-internal/build_common.rs +++ b/embassy-hal-internal/build_common.rs @@ -8,8 +8,6 @@ use std::collections::HashSet; use std::env; -use std::ffi::OsString; -use std::process::Command; /// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring /// them (`cargo:rust-check-cfg=cfg(X)`). @@ -17,7 +15,6 @@ use std::process::Command; pub struct CfgSet { enabled: HashSet, declared: HashSet, - emit_declared: bool, } impl CfgSet { @@ -25,7 +22,6 @@ impl CfgSet { Self { enabled: HashSet::new(), declared: HashSet::new(), - emit_declared: is_rustc_nightly(), } } @@ -49,7 +45,7 @@ impl CfgSet { /// /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. pub fn declare(&mut self, cfg: impl AsRef) { - if self.declared.insert(cfg.as_ref().to_owned()) && self.emit_declared { + if self.declared.insert(cfg.as_ref().to_owned()) { println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); } } @@ -69,21 +65,6 @@ impl CfgSet { } } -fn is_rustc_nightly() -> bool { - if env::var_os("EMBASSY_FORCE_CHECK_CFG").is_some() { - return true; - } - - let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); - - let output = Command::new(rustc) - .arg("--version") - .output() - .expect("failed to run `rustc --version`"); - - String::from_utf8_lossy(&output.stdout).contains("nightly") -} - /// Sets configs that describe the target platform. pub fn set_target_cfgs(cfgs: &mut CfgSet) { let target = env::var("TARGET").unwrap(); diff --git a/embassy-net-adin1110/Cargo.toml b/embassy-net-adin1110/Cargo.toml index e75f76b16..0b2a6744d 100644 --- a/embassy-net-adin1110/Cargo.toml +++ b/embassy-net-adin1110/Cargo.toml @@ -16,8 +16,8 @@ log = { version = "0.4", default-features = false, optional = true } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-bus = { version = "0.1", features = ["async"] } -embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" } -embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } bitfield = "0.14.0" diff --git a/embassy-net-adin1110/README.md b/embassy-net-adin1110/README.md index 39a38960d..0514274b4 100644 --- a/embassy-net-adin1110/README.md +++ b/embassy-net-adin1110/README.md @@ -21,7 +21,7 @@ APL can be used in [`intrinsic safety applications/explosion hazardous areas`](h ## Supported SPI modes -`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/download/document/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) +`ADIN1110` supports two SPI modes. `Generic` and [`OPEN Alliance 10BASE-T1x MAC-PHY serial interface`](https://opensig.org/wp-content/uploads/2023/12/OPEN_Alliance_10BASET1x_MAC-PHY_Serial_Interface_V1.1.pdf) Both modes support with and without additional CRC. Currently only `Generic` SPI with or without CRC is supported. diff --git a/embassy-net-driver-channel/CHANGELOG.md b/embassy-net-driver-channel/CHANGELOG.md index b04d0a86b..d7af7e55d 100644 --- a/embassy-net-driver-channel/CHANGELOG.md +++ b/embassy-net-driver-channel/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +## 0.3.0 - 2024-08-05 + +- Add collapse_debuginfo to fmt.rs macros. +- Update embassy-sync version + ## 0.2.0 - 2023-10-18 - Update `embassy-net-driver` to v0.2 diff --git a/embassy-net-driver-channel/Cargo.toml b/embassy-net-driver-channel/Cargo.toml index 3bd7d288a..abce9315b 100644 --- a/embassy-net-driver-channel/Cargo.toml +++ b/embassy-net-driver-channel/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-net-driver-channel" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT OR Apache-2.0" description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack." diff --git a/embassy-net-enc28j60/Cargo.toml b/embassy-net-enc28j60/Cargo.toml index dd7aa031d..cafced4b2 100644 --- a/embassy-net-enc28j60/Cargo.toml +++ b/embassy-net-enc28j60/Cargo.toml @@ -13,7 +13,7 @@ documentation = "https://docs.embassy.dev/embassy-net-enc28j60" embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } -embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } defmt = { version = "0.3", optional = true } diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml index 24b7d0806..915eba7a0 100644 --- a/embassy-net-esp-hosted/Cargo.toml +++ b/embassy-net-esp-hosted/Cargo.toml @@ -17,10 +17,10 @@ log = [ "dep:log" ] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } embassy-sync = { version = "0.6.0", path = "../embassy-sync"} embassy-futures = { version = "0.1.0", path = "../embassy-futures"} -embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs index c8cea8503..b1838a425 100644 --- a/embassy-net-esp-hosted/src/control.rs +++ b/embassy-net-esp-hosted/src/control.rs @@ -120,7 +120,7 @@ impl<'a> Control<'a> { pwd: unwrap!(String::try_from(password)), bssid: String::new(), listen_interval: 3, - is_wpa3_supported: false, + is_wpa3_supported: true, }; ioctl!(self, ReqConnectAp, RespConnectAp, req, resp); self.state_ch.set_link_state(LinkState::Up); diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs index c78578bf1..f05e2a70a 100644 --- a/embassy-net-esp-hosted/src/lib.rs +++ b/embassy-net-esp-hosted/src/lib.rs @@ -137,7 +137,7 @@ where let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); let state_ch = ch_runner.state_runner(); - let mut runner = Runner { + let runner = Runner { ch: ch_runner, state_ch, shared: &state.shared, @@ -148,7 +148,6 @@ where spi, heartbeat_deadline: Instant::now() + HEARTBEAT_MAX_GAP, }; - runner.init().await; (device, Control::new(state_ch, &state.shared), runner) } @@ -174,8 +173,6 @@ where IN: InputPin + Wait, OUT: OutputPin, { - async fn init(&mut self) {} - /// Run the packet processing. pub async fn run(mut self) -> ! { debug!("resetting..."); diff --git a/embassy-net-esp-hosted/src/proto.rs b/embassy-net-esp-hosted/src/proto.rs index 034d5bf84..089ded677 100644 --- a/embassy-net-esp-hosted/src/proto.rs +++ b/embassy-net-esp-hosted/src/proto.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use heapless::{String, Vec}; /// internal supporting structures for CtrlMsg diff --git a/embassy-net-nrf91/Cargo.toml b/embassy-net-nrf91/Cargo.toml new file mode 100644 index 000000000..07a0c8886 --- /dev/null +++ b/embassy-net-nrf91/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "embassy-net-nrf91" +version = "0.1.0" +edition = "2021" +description = "embassy-net driver for Nordic nRF91-series cellular modems" +keywords = ["embedded", "nrf91", "embassy-net", "cellular"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-nrf91" + +[features] +defmt = [ "dep:defmt", "heapless/defmt-03" ] +log = [ "dep:log" ] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +nrf9160-pac = { version = "0.12.0" } + +embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-sync = { version = "0.6.0", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} + +heapless = "0.8" +embedded-io = "0.6.1" +at-commands = "0.5.4" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-nrf91-v$VERSION/embassy-net-nrf91/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-nrf91/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy-net-nrf91/README.md b/embassy-net-nrf91/README.md new file mode 100644 index 000000000..30da71787 --- /dev/null +++ b/embassy-net-nrf91/README.md @@ -0,0 +1,9 @@ +# nRF91 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) driver for Nordic nRF91-series cellular modems. + +See the [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160) directory for usage examples with the nRF9160. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-net-nrf91/src/context.rs b/embassy-net-nrf91/src/context.rs new file mode 100644 index 000000000..8b45919ef --- /dev/null +++ b/embassy-net-nrf91/src/context.rs @@ -0,0 +1,350 @@ +//! Helper utility to configure a specific modem context. +use core::net::IpAddr; +use core::str::FromStr; + +use at_commands::builder::CommandBuilder; +use at_commands::parser::CommandParser; +use embassy_time::{Duration, Timer}; +use heapless::Vec; + +/// Provides a higher level API for controlling a given context. +pub struct Control<'a> { + control: crate::Control<'a>, + cid: u8, +} + +/// Configuration for a given context +pub struct Config<'a> { + /// Desired APN address. + pub apn: &'a [u8], + /// Desired authentication protocol. + pub auth_prot: AuthProt, + /// Credentials. + pub auth: Option<(&'a [u8], &'a [u8])>, +} + +/// Authentication protocol. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum AuthProt { + /// No authentication. + None = 0, + /// PAP authentication. + Pap = 1, + /// CHAP authentication. + Chap = 2, +} + +/// Error returned by control. +#[derive(Clone, Copy, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Not enough space for command. + BufferTooSmall, + /// Error parsing response from modem. + AtParseError, + /// Error parsing IP addresses. + AddrParseError, +} + +impl From for Error { + fn from(_: at_commands::parser::ParseError) -> Self { + Self::AtParseError + } +} + +/// Status of a given context. +#[derive(PartialEq, Debug)] +pub struct Status { + /// Attached to APN or not. + pub attached: bool, + /// IP if assigned. + pub ip: Option, + /// Gateway if assigned. + pub gateway: Option, + /// DNS servers if assigned. + pub dns: Vec, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for Status { + fn format(&self, f: defmt::Formatter<'_>) { + defmt::write!(f, "attached: {}", self.attached); + if let Some(ip) = &self.ip { + defmt::write!(f, ", ip: {}", defmt::Debug2Format(&ip)); + } + } +} + +impl<'a> Control<'a> { + /// Create a new instance of a control handle for a given context. + /// + /// Will wait for the modem to be initialized if not. + pub async fn new(control: crate::Control<'a>, cid: u8) -> Self { + control.wait_init().await; + Self { control, cid } + } + + /// Perform a raw AT command + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + self.control.at_command(req, resp).await + } + + /// Configures the modem with the provided config. + /// + /// NOTE: This will disconnect the modem from any current APN and should not + /// be called if the configuration has not been changed. + /// + /// After configuring, invoke [`enable()`] to activate the configuration. + pub async fn configure(&self, config: &Config<'_>) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGDCONT") + .with_int_parameter(self.cid) + .with_string_parameter("IP") + .with_string_parameter(config.apn) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + // info!("RES1: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + let mut op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGAUTH") + .with_int_parameter(self.cid) + .with_int_parameter(config.auth_prot as u8); + if let Some((username, password)) = config.auth { + op = op.with_string_parameter(username).with_string_parameter(password); + } + let op = op.finish().map_err(|_| Error::BufferTooSmall)?; + + let n = self.control.at_command(op, &mut buf).await; + // info!("RES2: {}", unsafe { core::str::from_utf8_unchecked(&buf[..n]) }); + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + Ok(()) + } + + /// Attach to the PDN + pub async fn attach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Read current connectivity status for modem. + pub async fn detach(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGATT") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + async fn attached(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res,) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + Ok(res == 1) + } + + /// Read current connectivity status for modem. + pub async fn status(&self) -> Result { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_query(&mut cmd, true) + .named("+CGATT") + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (res,) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGATT: ") + .expect_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + let attached = res == 1; + if !attached { + return Ok(Status { + attached, + ip: None, + gateway: None, + dns: Vec::new(), + }); + } + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGPADDR") + .with_int_parameter(self.cid) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_, ip1, _ip2) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGPADDR: ") + .expect_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + + let ip = if let Some(ip) = ip1 { + let ip = IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?; + Some(ip) + } else { + None + }; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CGCONTRDP") + .with_int_parameter(self.cid) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + let (_cid, _bid, _apn, _mask, gateway, dns1, dns2, _, _, _, _, _mtu) = CommandParser::parse(&buf[..n]) + .expect_identifier(b"+CGCONTRDP: ") + .expect_int_parameter() + .expect_optional_int_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_string_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_optional_int_parameter() + .expect_identifier(b"\r\nOK") + .finish()?; + + let gateway = if let Some(ip) = gateway { + if ip.is_empty() { + None + } else { + Some(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + } + } else { + None + }; + + let mut dns = Vec::new(); + if let Some(ip) = dns1 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); + } + + if let Some(ip) = dns2 { + dns.push(IpAddr::from_str(ip).map_err(|_| Error::AddrParseError)?) + .unwrap(); + } + + Ok(Status { + attached, + ip, + gateway, + dns, + }) + } + + async fn wait_attached(&self) -> Result { + while !self.attached().await? { + Timer::after(Duration::from_secs(1)).await; + } + let status = self.status().await?; + Ok(status) + } + + /// Disable modem + pub async fn disable(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(0) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + Ok(()) + } + + /// Enable modem + pub async fn enable(&self) -> Result<(), Error> { + let mut cmd: [u8; 256] = [0; 256]; + let mut buf: [u8; 256] = [0; 256]; + + let op = CommandBuilder::create_set(&mut cmd, true) + .named("+CFUN") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + + // Make modem survive PDN detaches + let op = CommandBuilder::create_set(&mut cmd, true) + .named("%XPDNCFG") + .with_int_parameter(1) + .finish() + .map_err(|_| Error::BufferTooSmall)?; + let n = self.control.at_command(op, &mut buf).await; + CommandParser::parse(&buf[..n]).expect_identifier(b"OK").finish()?; + Ok(()) + } + + /// Run a control loop for this context, ensuring that reaattach is handled. + pub async fn run(&self, reattach: F) -> Result<(), Error> { + self.enable().await?; + let status = self.wait_attached().await?; + let mut fd = self.control.open_raw_socket().await; + reattach(&status); + + loop { + if !self.attached().await? { + trace!("detached"); + + self.control.close_raw_socket(fd).await; + let status = self.wait_attached().await?; + trace!("attached"); + fd = self.control.open_raw_socket().await; + reattach(&status); + } + Timer::after(Duration::from_secs(10)).await; + } + } +} diff --git a/embassy-net-nrf91/src/fmt.rs b/embassy-net-nrf91/src/fmt.rs new file mode 100644 index 000000000..35b929fde --- /dev/null +++ b/embassy-net-nrf91/src/fmt.rs @@ -0,0 +1,274 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-nrf91/src/lib.rs b/embassy-net-nrf91/src/lib.rs new file mode 100644 index 000000000..60cdc38c6 --- /dev/null +++ b/embassy-net-nrf91/src/lib.rs @@ -0,0 +1,1046 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] +#![deny(unused_must_use)] + +// must be first +mod fmt; + +pub mod context; + +use core::cell::RefCell; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem::{self, MaybeUninit}; +use core::ptr::{self, addr_of, addr_of_mut, copy_nonoverlapping}; +use core::slice; +use core::sync::atomic::{compiler_fence, fence, Ordering}; +use core::task::{Poll, Waker}; + +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pipe; +use embassy_sync::waitqueue::{AtomicWaker, WakerRegistration}; +use heapless::Vec; +use pac::NVIC; +use {embassy_net_driver_channel as ch, nrf9160_pac as pac}; + +const RX_SIZE: usize = 8 * 1024; +const TRACE_SIZE: usize = 16 * 1024; +const TRACE_BUF: usize = 1024; +const MTU: usize = 1500; + +/// Network driver. +/// +/// This is the type you have to pass to `embassy-net` when creating the network stack. +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// Call this function on IPC IRQ +pub fn on_ipc_irq() { + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + + trace!("irq"); + + ipc.inten.write(|w| w); + WAKER.wake(); +} + +struct Allocator<'a> { + start: *mut u8, + end: *mut u8, + _phantom: PhantomData<&'a mut u8>, +} + +impl<'a> Allocator<'a> { + fn alloc_bytes(&mut self, size: usize) -> &'a mut [MaybeUninit] { + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = self.start; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is in-bounds. + unsafe { slice::from_raw_parts_mut(p as *mut _, size) } + } + + fn alloc(&mut self) -> &'a mut MaybeUninit { + let align = mem::align_of::(); + let size = mem::size_of::(); + + let align_size = match (self.start as usize) % align { + 0 => 0, + n => align - n, + }; + + // safety: both pointers come from the same allocation. + let available_size = unsafe { self.end.offset_from(self.start) } as usize; + if align_size + size > available_size { + panic!("out of memory") + } + + // safety: we've checked above this doesn't go out of bounds. + let p = unsafe { self.start.add(align_size) }; + self.start = unsafe { p.add(size) }; + + // safety: we've checked the pointer is aligned and in-bounds. + unsafe { &mut *(p as *mut _) } + } +} + +/// Create a new nRF91 embassy-net driver. +pub async fn new<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], +) -> (NetDriver<'a>, Control<'a>, Runner<'a>) { + let (n, c, r, _) = new_internal(state, shmem, None).await; + (n, c, r) +} + +/// Create a new nRF91 embassy-net driver with trace. +pub async fn new_with_trace<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: &'a mut TraceBuffer, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, TraceReader<'a>) { + let (n, c, r, t) = new_internal(state, shmem, Some(trace_buffer)).await; + (n, c, r, t.unwrap()) +} + +/// Create a new nRF91 embassy-net driver. +async fn new_internal<'a>( + state: &'a mut State, + shmem: &'a mut [MaybeUninit], + trace_buffer: Option<&'a mut TraceBuffer>, +) -> (NetDriver<'a>, Control<'a>, Runner<'a>, Option>) { + let shmem_len = shmem.len(); + let shmem_ptr = shmem.as_mut_ptr() as *mut u8; + + const SPU_REGION_SIZE: usize = 8192; // 8kb + assert!(shmem_len != 0); + assert!( + shmem_len % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize) % SPU_REGION_SIZE == 0, + "shmem length must be a multiple of 8kb" + ); + assert!( + (shmem_ptr as usize + shmem_len) < 0x2002_0000, + "shmem must be in the lower 128kb of RAM" + ); + + let spu = unsafe { &*pac::SPU_S::ptr() }; + debug!("Setting IPC RAM as nonsecure..."); + let region_start = (shmem_ptr as usize - 0x2000_0000) / SPU_REGION_SIZE; + let region_end = region_start + shmem_len / SPU_REGION_SIZE; + for i in region_start..region_end { + spu.ramregion[i].perm.write(|w| { + w.execute().set_bit(); + w.write().set_bit(); + w.read().set_bit(); + w.secattr().clear_bit(); + w.lock().clear_bit(); + w + }) + } + + spu.periphid[42].perm.write(|w| w.secattr().non_secure()); + + let mut alloc = Allocator { + start: shmem_ptr, + end: unsafe { shmem_ptr.add(shmem_len) }, + _phantom: PhantomData, + }; + + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + let power = unsafe { &*pac::POWER_S::ptr() }; + + let cb: &mut ControlBlock = alloc.alloc().write(unsafe { mem::zeroed() }); + let rx = alloc.alloc_bytes(RX_SIZE); + let trace = alloc.alloc_bytes(TRACE_SIZE); + + cb.version = 0x00010000; + cb.rx_base = rx.as_mut_ptr() as _; + cb.rx_size = RX_SIZE; + cb.control_list_ptr = &mut cb.lists[0]; + cb.data_list_ptr = &mut cb.lists[1]; + cb.modem_info_ptr = &mut cb.modem_info; + cb.trace_ptr = &mut cb.trace; + cb.lists[0].len = LIST_LEN; + cb.lists[1].len = LIST_LEN; + cb.trace.base = trace.as_mut_ptr() as _; + cb.trace.size = TRACE_SIZE; + + ipc.gpmem[0].write(|w| unsafe { w.bits(cb as *mut _ as u32) }); + ipc.gpmem[1].write(|w| unsafe { w.bits(0) }); + + // connect task/event i to channel i + for i in 0..8 { + ipc.send_cnf[i].write(|w| unsafe { w.bits(1 << i) }); + ipc.receive_cnf[i].write(|w| unsafe { w.bits(1 << i) }); + } + + compiler_fence(Ordering::SeqCst); + + // POWER.LTEMODEM.STARTN = 0 + // The reg is missing in the PAC?? + let startn = unsafe { (power as *const _ as *mut u32).add(0x610 / 4) }; + unsafe { startn.write_volatile(0) } + + unsafe { NVIC::unmask(pac::Interrupt::IPC) }; + + let state_inner = &*state.inner.write(RefCell::new(StateInner { + init: false, + init_waker: WakerRegistration::new(), + cb, + requests: [const { None }; REQ_COUNT], + next_req_serial: 0x12345678, + + rx_control_list: ptr::null_mut(), + rx_data_list: ptr::null_mut(), + rx_seq_no: 0, + rx_check: PointerChecker { + start: rx.as_mut_ptr() as *mut u8, + end: (rx.as_mut_ptr() as *mut u8).wrapping_add(RX_SIZE), + }, + + tx_seq_no: 0, + tx_buf_used: [false; TX_BUF_COUNT], + + trace_chans: Vec::new(), + trace_check: PointerChecker { + start: trace.as_mut_ptr() as *mut u8, + end: (trace.as_mut_ptr() as *mut u8).wrapping_add(TRACE_SIZE), + }, + })); + + let control = Control { state: state_inner }; + + let (ch_runner, device) = ch::new(&mut state.ch, ch::driver::HardwareAddress::Ip); + let state_ch = ch_runner.state_runner(); + state_ch.set_link_state(ch::driver::LinkState::Up); + + let (trace_reader, trace_writer) = if let Some(trace) = trace_buffer { + let (r, w) = trace.trace.split(); + (Some(r), Some(w)) + } else { + (None, None) + }; + + let runner = Runner { + ch: ch_runner, + state: state_inner, + trace_writer, + }; + + (device, control, runner, trace_reader) +} + +/// State holding modem traces. +pub struct TraceBuffer { + trace: pipe::Pipe, +} + +/// Represents writer half of the trace buffer. +pub type TraceWriter<'a> = pipe::Writer<'a, NoopRawMutex, TRACE_BUF>; + +/// Represents the reader half of the trace buffer. +pub type TraceReader<'a> = pipe::Reader<'a, NoopRawMutex, TRACE_BUF>; + +impl TraceBuffer { + /// Create a new TraceBuffer. + pub const fn new() -> Self { + Self { + trace: pipe::Pipe::new(), + } + } +} + +/// Shared state for the driver. +pub struct State { + ch: ch::State, + inner: MaybeUninit>, +} + +impl State { + /// Create a new State. + pub const fn new() -> Self { + Self { + ch: ch::State::new(), + inner: MaybeUninit::uninit(), + } + } +} + +const TX_BUF_COUNT: usize = 4; +const TX_BUF_SIZE: usize = 1500; + +struct TraceChannelInfo { + ptr: *mut TraceChannel, + start: *mut u8, + end: *mut u8, +} + +const REQ_COUNT: usize = 4; + +struct PendingRequest { + req_serial: u32, + resp_msg: *mut Message, + waker: Waker, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct NoFreeBufs; + +struct StateInner { + init: bool, + init_waker: WakerRegistration, + + cb: *mut ControlBlock, + requests: [Option; REQ_COUNT], + next_req_serial: u32, + + rx_control_list: *mut List, + rx_data_list: *mut List, + rx_seq_no: u16, + rx_check: PointerChecker, + + tx_seq_no: u16, + tx_buf_used: [bool; TX_BUF_COUNT], + + trace_chans: Vec, + trace_check: PointerChecker, +} + +impl StateInner { + fn poll(&mut self, trace_writer: &mut Option>, ch: &mut ch::Runner) { + trace!("poll!"); + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + + if ipc.events_receive[0].read().bits() != 0 { + ipc.events_receive[0].reset(); + trace!("ipc 0"); + } + + if ipc.events_receive[2].read().bits() != 0 { + ipc.events_receive[2].reset(); + trace!("ipc 2"); + + if !self.init { + let desc = unsafe { addr_of!((*self.cb).modem_info).read_volatile() }; + assert_eq!(desc.version, 1); + + self.rx_check.check_mut(desc.control_list_ptr); + self.rx_check.check_mut(desc.data_list_ptr); + + self.rx_control_list = desc.control_list_ptr; + self.rx_data_list = desc.data_list_ptr; + let rx_control_len = unsafe { addr_of!((*self.rx_control_list).len).read_volatile() }; + let rx_data_len = unsafe { addr_of!((*self.rx_data_list).len).read_volatile() }; + assert_eq!(rx_control_len, LIST_LEN); + assert_eq!(rx_data_len, LIST_LEN); + self.init = true; + + debug!("IPC initialized OK!"); + self.init_waker.wake(); + } + } + + if ipc.events_receive[4].read().bits() != 0 { + ipc.events_receive[4].reset(); + trace!("ipc 4"); + + loop { + let list = unsafe { &mut *self.rx_control_list }; + let control_work = self.process(list, true, ch); + let list = unsafe { &mut *self.rx_data_list }; + let data_work = self.process(list, false, ch); + if !control_work && !data_work { + break; + } + } + } + + if ipc.events_receive[6].read().bits() != 0 { + ipc.events_receive[6].reset(); + trace!("ipc 6"); + } + + if ipc.events_receive[7].read().bits() != 0 { + ipc.events_receive[7].reset(); + trace!("ipc 7: trace"); + + let msg = unsafe { addr_of!((*self.cb).trace.rx_state).read_volatile() }; + if msg != 0 { + trace!("trace msg {}", msg); + match msg { + 0 => unreachable!(), + 1 => { + let ctx = unsafe { addr_of!((*self.cb).trace.rx_ptr).read_volatile() } as *mut TraceContext; + debug!("trace init: {:?}", ctx); + self.trace_check.check(ctx); + let chans = unsafe { addr_of!((*ctx).chans).read_volatile() }; + for chan_ptr in chans { + let chan = self.trace_check.check_read(chan_ptr); + self.trace_check.check(chan.start); + self.trace_check.check(chan.end); + assert!(chan.start < chan.end); + self.trace_chans + .push(TraceChannelInfo { + ptr: chan_ptr, + start: chan.start, + end: chan.end, + }) + .map_err(|_| ()) + .unwrap() + } + } + 2 => { + for chan_info in &self.trace_chans { + let read_ptr = unsafe { addr_of!((*chan_info.ptr).read_ptr).read_volatile() }; + let write_ptr = unsafe { addr_of!((*chan_info.ptr).write_ptr).read_volatile() }; + assert!(read_ptr >= chan_info.start && read_ptr <= chan_info.end); + assert!(write_ptr >= chan_info.start && write_ptr <= chan_info.end); + if read_ptr != write_ptr { + let id = unsafe { addr_of!((*chan_info.ptr).id).read_volatile() }; + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + if read_ptr < write_ptr { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, write_ptr.offset_from(read_ptr) as _) + }); + } else { + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts(read_ptr, chan_info.end.offset_from(read_ptr) as _) + }); + Self::handle_trace(trace_writer, id, unsafe { + slice::from_raw_parts( + chan_info.start, + write_ptr.offset_from(chan_info.start) as _, + ) + }); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the slice access. + unsafe { addr_of_mut!((*chan_info.ptr).read_ptr).write_volatile(write_ptr) }; + } + } + } + _ => warn!("unknown trace msg {}", msg), + } + unsafe { addr_of_mut!((*self.cb).trace.rx_state).write_volatile(0) }; + } + } + + ipc.intenset.write(|w| { + w.receive0().set_bit(); + w.receive2().set_bit(); + w.receive4().set_bit(); + w.receive6().set_bit(); + w.receive7().set_bit(); + w + }); + } + + fn handle_trace(writer: &mut Option>, id: u8, data: &[u8]) { + if let Some(writer) = writer { + trace!("trace: {} {}", id, data.len()); + let mut header = [0u8; 5]; + header[0] = 0xEF; + header[1] = 0xBE; + header[2..4].copy_from_slice(&(data.len() as u16).to_le_bytes()); + header[4] = id; + writer.try_write(&header).ok(); + writer.try_write(data).ok(); + } + } + + fn process(&mut self, list: *mut List, is_control: bool, ch: &mut ch::Runner) -> bool { + let mut did_work = false; + for i in 0..LIST_LEN { + let item_ptr = unsafe { addr_of_mut!((*list).items[i]) }; + let preamble = unsafe { addr_of!((*item_ptr).state).read_volatile() }; + if preamble & 0xFF == 0x01 && preamble >> 16 == self.rx_seq_no as u32 { + let msg_ptr = unsafe { addr_of!((*item_ptr).message).read_volatile() }; + let msg = self.rx_check.check_read(msg_ptr); + + debug!("rx seq {} msg: {:?}", preamble >> 16, msg); + + if is_control { + self.handle_control(&msg); + } else { + self.handle_data(&msg, ch); + } + + unsafe { addr_of_mut!((*item_ptr).state).write_volatile(0x03) }; + self.rx_seq_no = self.rx_seq_no.wrapping_add(1); + + did_work = true; + } + } + did_work + } + + fn find_free_message(&mut self, ch: usize) -> Option { + for i in 0..LIST_LEN { + let preamble = unsafe { addr_of!((*self.cb).lists[ch].items[i].state).read_volatile() }; + if matches!(preamble & 0xFF, 0 | 3) { + trace!("using tx msg idx {}", i); + return Some(i); + } + } + return None; + } + + fn find_free_tx_buf(&mut self) -> Option { + for i in 0..TX_BUF_COUNT { + if !self.tx_buf_used[i] { + trace!("using tx buf idx {}", i); + return Some(i); + } + } + return None; + } + + fn send_message(&mut self, msg: &mut Message, data: &[u8]) -> Result<(), NoFreeBufs> { + if data.is_empty() { + msg.data = ptr::null_mut(); + msg.data_len = 0; + } else { + assert!(data.len() <= TX_BUF_SIZE); + let buf_idx = self.find_free_tx_buf().ok_or(NoFreeBufs)?; + let buf = unsafe { addr_of_mut!((*self.cb).tx_bufs[buf_idx]) } as *mut u8; + unsafe { copy_nonoverlapping(data.as_ptr(), buf, data.len()) } + msg.data = buf; + msg.data_len = data.len(); + self.tx_buf_used[buf_idx] = true; + + fence(Ordering::SeqCst); // synchronize copy_nonoverlapping (non-volatile) with volatile writes below. + } + + // TODO free data buf if send_message_raw fails. + self.send_message_raw(msg) + } + + fn send_message_raw(&mut self, msg: &Message) -> Result<(), NoFreeBufs> { + let (ch, ipc_ch) = match msg.channel { + 1 => (0, 1), // control + 2 => (1, 3), // data + _ => unreachable!(), + }; + + // allocate a msg. + let idx = self.find_free_message(ch).ok_or(NoFreeBufs)?; + + debug!("tx seq {} msg: {:?}", self.tx_seq_no, msg); + + let msg_slot = unsafe { addr_of_mut!((*self.cb).msgs[ch][idx]) }; + unsafe { msg_slot.write_volatile(*msg) } + let list_item = unsafe { addr_of_mut!((*self.cb).lists[ch].items[idx]) }; + unsafe { addr_of_mut!((*list_item).message).write_volatile(msg_slot) } + unsafe { addr_of_mut!((*list_item).state).write_volatile((self.tx_seq_no as u32) << 16 | 0x01) } + self.tx_seq_no = self.tx_seq_no.wrapping_add(1); + + let ipc = unsafe { &*pac::IPC_NS::ptr() }; + ipc.tasks_send[ipc_ch].write(|w| unsafe { w.bits(1) }); + Ok(()) + } + + fn handle_control(&mut self, msg: &Message) { + match msg.id >> 16 { + 1 => debug!("control msg: modem ready"), + 2 => self.handle_control_free(msg.data), + _ => warn!("unknown control message id {:08x}", msg.id), + } + } + + fn handle_control_free(&mut self, ptr: *mut u8) { + let base = unsafe { addr_of!((*self.cb).tx_bufs) } as usize; + let ptr = ptr as usize; + + if ptr < base { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + let diff = ptr - base; + let idx = diff / TX_BUF_SIZE; + + if idx >= TX_BUF_COUNT || idx * TX_BUF_SIZE != diff { + warn!("control free bad pointer {:08x}", ptr); + return; + } + + trace!("control free pointer {:08x} idx {}", ptr, idx); + if !self.tx_buf_used[idx] { + warn!( + "control free pointer {:08x} idx {}: buffer was already free??", + ptr, idx + ); + } + self.tx_buf_used[idx] = false; + } + + fn handle_data(&mut self, msg: &Message, ch: &mut ch::Runner) { + if !msg.data.is_null() { + self.rx_check.check_length(msg.data, msg.data_len); + } + + let freed = match msg.id & 0xFFFF { + // AT + 3 => { + match msg.id >> 16 { + // AT request ack + 2 => false, + // AT response + 3 => self.handle_resp(msg), + // AT notification + 4 => false, + x => { + warn!("received unknown AT kind {}", x); + false + } + } + } + // IP + 4 => { + match msg.id >> 28 { + // IP response + 8 => self.handle_resp(msg), + // IP notification + 9 => match (msg.id >> 16) & 0xFFF { + // IP receive notification + 1 => { + if let Some(buf) = ch.try_rx_buf() { + let mut len = msg.data_len; + if len > buf.len() { + warn!("truncating rx'd packet from {} to {} bytes", len, buf.len()); + len = buf.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, buf.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + ch.rx_done(len); + } + false + } + _ => false, + }, + x => { + warn!("received unknown IP kind {}", x); + false + } + } + } + x => { + warn!("received unknown kind {}", x); + false + } + }; + + if !freed { + self.send_free(msg); + } + } + + fn handle_resp(&mut self, msg: &Message) -> bool { + let req_serial = u32::from_le_bytes(msg.param[0..4].try_into().unwrap()); + if req_serial == 0 { + return false; + } + + for optr in &mut self.requests { + if let Some(r) = optr { + if r.req_serial == req_serial { + let r = optr.take().unwrap(); + unsafe { r.resp_msg.write(*msg) } + r.waker.wake(); + *optr = None; + return true; + } + } + } + + warn!( + "resp with id {} serial {} doesn't match any pending req", + msg.id, req_serial + ); + false + } + + fn send_free(&mut self, msg: &Message) { + if msg.data.is_null() { + return; + } + + let mut free_msg: Message = unsafe { mem::zeroed() }; + free_msg.channel = 1; // control + free_msg.id = 0x20001; // free + free_msg.data = msg.data; + free_msg.data_len = msg.data_len; + + unwrap!(self.send_message_raw(&free_msg)); + } +} + +struct PointerChecker { + start: *mut u8, + end: *mut u8, +} + +impl PointerChecker { + // check the pointer is in bounds in the arena, panic otherwise. + fn check_length(&self, ptr: *const u8, len: usize) { + assert!(ptr as usize >= self.start as usize); + let end_ptr = (ptr as usize).checked_add(len).unwrap(); + assert!(end_ptr <= self.end as usize); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check(&self, ptr: *const T) { + assert!(ptr.is_aligned()); + self.check_length(ptr as *const u8, mem::size_of::()); + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_read(&self, ptr: *const T) -> T { + self.check(ptr); + unsafe { ptr.read_volatile() } + } + + // check the pointer is in bounds in the arena, panic otherwise. + fn check_mut(&self, ptr: *mut T) { + self.check(ptr as *const T) + } +} + +/// Control handle for the driver. +/// +/// You can use this object to control the modem at runtime, such as running AT commands. +pub struct Control<'a> { + state: &'a RefCell, +} + +impl<'a> Control<'a> { + /// Wait for modem IPC to be initialized. + pub async fn wait_init(&self) { + poll_fn(|cx| { + let mut state = self.state.borrow_mut(); + if state.init { + return Poll::Ready(()); + } + state.init_waker.register(cx.waker()); + Poll::Pending + }) + .await + } + + async fn request(&self, msg: &mut Message, req_data: &[u8], resp_data: &mut [u8]) -> usize { + // get waker + let waker = poll_fn(|cx| Poll::Ready(cx.waker().clone())).await; + + // Send request + let mut state = self.state.borrow_mut(); + let mut req_serial = state.next_req_serial; + if msg.id & 0xFFFF == 3 { + // AT response seems to keep only the lower 8 bits. Others do keep the full 32 bits..?? + req_serial &= 0xFF; + } + + // increment next_req_serial, skip zero because we use it as an "ignore" value. + // We have to skip when the *lowest byte* is zero because AT responses. + state.next_req_serial = state.next_req_serial.wrapping_add(1); + if state.next_req_serial & 0xFF == 0 { + state.next_req_serial = state.next_req_serial.wrapping_add(1); + } + + msg.param[0..4].copy_from_slice(&req_serial.to_le_bytes()); + unwrap!(state.send_message(msg, req_data)); + + // Setup the pending request state. + let (req_slot_idx, req_slot) = state + .requests + .iter_mut() + .enumerate() + .find(|(_, x)| x.is_none()) + .unwrap(); + msg.id = 0; // zero out id, so when it becomes nonzero we know the req is done. + let msg_ptr: *mut Message = msg; + *req_slot = Some(PendingRequest { + req_serial, + resp_msg: msg_ptr, + waker, + }); + + drop(state); // don't borrow state across awaits. + + // On cancel, unregister the request slot. + let _drop = OnDrop::new(|| { + // Remove request slot. + let mut state = self.state.borrow_mut(); + let slot = &mut state.requests[req_slot_idx]; + if let Some(s) = slot { + if s.req_serial == req_serial { + *slot = None; + } + } + + // If cancelation raced with actually receiving the response, + // we own the data, so we have to free it. + let msg = unsafe { &mut *msg_ptr }; + if msg.id != 0 { + state.send_free(msg); + } + }); + // Wait for response. + poll_fn(|_| { + // we have to use the raw pointer and not the original reference `msg` + // because that'd invalidate the raw ptr that's still stored in `req_slot`. + if unsafe { (*msg_ptr).id } != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + _drop.defuse(); + + if msg.data.is_null() { + // no response data. + return 0; + } + + // Copy response data out, if any. + // Pointer was validated in StateInner::handle_data(). + let mut len = msg.data_len; + if len > resp_data.len() { + warn!("truncating response data from {} to {}", len, resp_data.len()); + len = resp_data.len(); + } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + unsafe { ptr::copy_nonoverlapping(msg.data, resp_data.as_mut_ptr(), len) } + fence(Ordering::SeqCst); // synchronize volatile accesses with the nonvolatile copy_nonoverlapping. + self.state.borrow_mut().send_free(msg); + len + } + + /// Run an AT command. + /// + /// The response is written in `resp` and its length returned. + pub async fn at_command(&self, req: &[u8], resp: &mut [u8]) -> usize { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x0001_0003; // AT command + msg.param_len = 4; + + self.request(&mut msg, req, resp).await + } + + /// Open the raw socket used for sending/receiving IP packets. + /// + /// This must be done after `AT+CFUN=1` (?) + async fn open_raw_socket(&self) -> u32 { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7001_0004; // open socket + msg.param_len = 20; + + let param = [ + 0xFF, 0xFF, 0xFF, 0xFF, // req_serial + 0xFF, 0xFF, 0xFF, 0xFF, // ??? + 0x05, 0x00, 0x00, 0x00, // family + 0x03, 0x00, 0x00, 0x00, // type + 0x00, 0x00, 0x00, 0x00, // protocol + ]; + msg.param[..param.len()].copy_from_slice(¶m); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80010004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + assert_eq!(msg.param_len, 16); + let fd = u32::from_le_bytes(msg.param[12..16].try_into().unwrap()); + trace!("got FD: {}", fd); + fd + } + + async fn close_raw_socket(&self, fd: u32) { + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7009_0004; // close socket + msg.param_len = 8; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + + self.request(&mut msg, &[], &mut []).await; + + assert_eq!(msg.id, 0x80090004); + assert!(msg.param_len >= 12); + let status = u32::from_le_bytes(msg.param[8..12].try_into().unwrap()); + assert_eq!(status, 0); + } +} + +/// Background runner for the driver. +pub struct Runner<'a> { + ch: ch::Runner<'a, MTU>, + state: &'a RefCell, + trace_writer: Option>, +} + +impl<'a> Runner<'a> { + /// Run the driver operation in the background. + /// + /// You must run this in a background task, concurrently with all network operations. + pub async fn run(mut self) -> ! { + poll_fn(|cx| { + WAKER.register(cx.waker()); + + let mut state = self.state.borrow_mut(); + state.poll(&mut self.trace_writer, &mut self.ch); + + if let Poll::Ready(buf) = self.ch.poll_tx_buf(cx) { + let fd = 128u32; // TODO unhardcode + let mut msg: Message = unsafe { mem::zeroed() }; + msg.channel = 2; // data + msg.id = 0x7006_0004; // IP send + msg.param_len = 12; + msg.param[4..8].copy_from_slice(&fd.to_le_bytes()); + if let Err(e) = state.send_message(&mut msg, buf) { + warn!("tx failed: {:?}", e); + } + self.ch.tx_done(); + } + + Poll::Pending + }) + .await + } +} + +const LIST_LEN: usize = 16; + +#[repr(C)] +struct ControlBlock { + version: u32, + rx_base: *mut u8, + rx_size: usize, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + modem_info_ptr: *mut ModemInfo, + trace_ptr: *mut Trace, + unk: u32, + + modem_info: ModemInfo, + trace: Trace, + + // 0 = control, 1 = data + lists: [List; 2], + msgs: [[Message; LIST_LEN]; 2], + + tx_bufs: [[u8; TX_BUF_SIZE]; TX_BUF_COUNT], +} + +#[repr(C)] +struct ModemInfo { + version: u32, + control_list_ptr: *mut List, + data_list_ptr: *mut List, + padding: [u32; 5], +} + +#[repr(C)] +struct Trace { + size: usize, + base: *mut u8, + tx_state: u32, + tx_ptr: *mut u8, + rx_state: u32, + rx_ptr: *mut u8, + unk1: u32, + unk2: u32, +} + +const TRACE_CHANNEL_COUNT: usize = 3; + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceContext { + unk1: u32, + unk2: u32, + len: u32, + chans: [*mut TraceChannel; TRACE_CHANNEL_COUNT], +} + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct TraceChannel { + id: u8, + unk1: u8, + unk2: u8, + unk3: u8, + write_ptr: *mut u8, + read_ptr: *mut u8, + start: *mut u8, + end: *mut u8, +} + +#[repr(C)] +struct List { + len: usize, + items: [ListItem; LIST_LEN], +} + +#[repr(C)] +struct ListItem { + /// top 16 bits: seqno + /// bottom 8 bits: + /// 0x01: sent + /// 0x02: held + /// 0x03: freed + state: u32, + message: *mut Message, +} + +#[repr(C)] +#[derive(defmt::Format, Clone, Copy)] +struct Message { + id: u32, + + /// 1 = control, 2 = data + channel: u8, + unk1: u8, + unk2: u8, + unk3: u8, + + data: *mut u8, + data_len: usize, + param_len: usize, + param: [u8; 44], +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy-net-ppp/Cargo.toml b/embassy-net-ppp/Cargo.toml index cdfafaae1..f6371f955 100644 --- a/embassy-net-ppp/Cargo.toml +++ b/embassy-net-ppp/Cargo.toml @@ -18,7 +18,7 @@ defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } embedded-io-async = { version = "0.6.1" } -embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } ppproto = { version = "0.1.2"} embassy-sync = { version = "0.6.0", path = "../embassy-sync" } diff --git a/embassy-net-wiznet/Cargo.toml b/embassy-net-wiznet/Cargo.toml index b6ce20325..e7fb3f455 100644 --- a/embassy-net-wiznet/Cargo.toml +++ b/embassy-net-wiznet/Cargo.toml @@ -12,8 +12,8 @@ documentation = "https://docs.embassy.dev/embassy-net-wiznet" [dependencies] embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } -embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" } -embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } defmt = { version = "0.3", optional = true } diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 15b97af47..2e21b4231 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -16,11 +16,11 @@ categories = [ [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/" -features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] +features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] target = "thumbv7em-none-eabi" [package.metadata.docs.rs] -features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] +features = ["defmt", "tcp", "udp", "raw", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] [features] default = [] @@ -44,6 +44,8 @@ raw = ["smoltcp/socket-raw"] tcp = ["smoltcp/socket-tcp"] ## Enable DNS support dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"] +## Enable mDNS support +mdns = ["dns", "smoltcp/socket-mdns"] ## Enable DHCPv4 support dhcpv4 = ["proto-ipv4", "medium-ethernet", "smoltcp/socket-dhcpv4"] ## Enable DHCPv4 support with hostname @@ -58,21 +60,21 @@ medium-ethernet = ["smoltcp/medium-ethernet"] medium-ip = ["smoltcp/medium-ip"] ## Enable the IEEE 802.15.4 medium medium-ieee802154 = ["smoltcp/medium-ieee802154"] -## Enable IGMP support -igmp = ["smoltcp/proto-igmp"] +## Enable multicast support (for both ipv4 and/or ipv6 if enabled) +multicast = ["smoltcp/multicast"] [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -smoltcp = { version = "0.11.0", default-features = false, features = [ +smoltcp = { git="https://github.com/smoltcp-rs/smoltcp", rev="dd43c8f189178b0ab3bda798ed8578b5b0a6f094", default-features = false, features = [ "socket", "async", ] } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } -embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } embedded-io-async = { version = "0.6.1" } diff --git a/embassy-net/README.md b/embassy-net/README.md index 6b7d46934..1722ffc7b 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -10,8 +10,9 @@ memory management designed to work well for embedded systems, aiming for a more - IPv4, IPv6 - Ethernet and bare-IP mediums. -- TCP, UDP, DNS, DHCPv4, IGMPv4 +- TCP, UDP, DNS, DHCPv4 - TCP sockets implement the `embedded-io` async traits. +- Multicast See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and unimplemented features of the network protocols. diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs index 8ccfa4e4f..1fbaea4f0 100644 --- a/embassy-net/src/dns.rs +++ b/embassy-net/src/dns.rs @@ -9,7 +9,7 @@ pub use smoltcp::socket::dns::{DnsQuery, Socket}; pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; pub use smoltcp::wire::{DnsQueryType, IpAddress}; -use crate::{Driver, Stack}; +use crate::Stack; /// Errors returned by DnsSocket. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -44,21 +44,15 @@ impl From for Error { /// This exists only for compatibility with crates that use `embedded-nal-async`. /// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're /// not using `embedded-nal-async`. -pub struct DnsSocket<'a, D> -where - D: Driver + 'static, -{ - stack: &'a Stack, +pub struct DnsSocket<'a> { + stack: Stack<'a>, } -impl<'a, D> DnsSocket<'a, D> -where - D: Driver + 'static, -{ +impl<'a> DnsSocket<'a> { /// Create a new DNS socket using the provided stack. /// /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. - pub fn new(stack: &'a Stack) -> Self { + pub fn new(stack: Stack<'a>) -> Self { Self { stack } } @@ -72,10 +66,7 @@ where } } -impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D> -where - D: Driver + 'static, -{ +impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { type Error = Error; async fn get_host_by_name( @@ -124,3 +115,7 @@ where todo!() } } + +fn _assert_covariant<'a, 'b: 'a>(x: DnsSocket<'b>) -> DnsSocket<'a> { + x +} diff --git a/embassy-net/src/device.rs b/embassy-net/src/driver_util.rs similarity index 98% rename from embassy-net/src/device.rs rename to embassy-net/src/driver_util.rs index 3b1d3c47c..b2af1d499 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/driver_util.rs @@ -74,7 +74,7 @@ where { fn consume(self, f: F) -> R where - F: FnOnce(&mut [u8]) -> R, + F: FnOnce(&[u8]) -> R, { self.0.consume(|buf| { #[cfg(feature = "packet-trace")] diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 1448dbc1f..a7b7efa87 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -12,9 +12,9 @@ compile_error!("You must enable at least one of the following features: proto-ip // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -mod device; #[cfg(feature = "dns")] pub mod dns; +mod driver_util; #[cfg(feature = "raw")] pub mod raw; #[cfg(feature = "tcp")] @@ -25,6 +25,7 @@ pub mod udp; use core::cell::RefCell; use core::future::{poll_fn, Future}; +use core::mem::MaybeUninit; use core::pin::pin; use core::task::{Context, Poll}; @@ -34,7 +35,9 @@ use embassy_sync::waitqueue::WakerRegistration; use embassy_time::{Instant, Timer}; #[allow(unused_imports)] use heapless::Vec; -#[cfg(feature = "igmp")] +#[cfg(feature = "dns")] +pub use smoltcp::config::DNS_MAX_SERVER_COUNT; +#[cfg(feature = "multicast")] pub use smoltcp::iface::MulticastError; #[allow(unused_imports)] use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; @@ -55,7 +58,7 @@ pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; #[cfg(feature = "proto-ipv6")] pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; -use crate::device::DriverAdapter; +use crate::driver_util::DriverAdapter; use crate::time::{instant_from_smoltcp, instant_to_smoltcp}; const LOCAL_PORT_MIN: u16 = 1025; @@ -67,33 +70,33 @@ const MAX_HOSTNAME_LEN: usize = 32; /// Memory resources needed for a network stack. pub struct StackResources { - sockets: [SocketStorage<'static>; SOCK], + sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>, + inner: MaybeUninit>, #[cfg(feature = "dns")] - queries: [Option; MAX_QUERIES], + queries: MaybeUninit<[Option; MAX_QUERIES]>, #[cfg(feature = "dhcpv4-hostname")] - hostname: core::cell::UnsafeCell, + hostname: HostnameResources, } #[cfg(feature = "dhcpv4-hostname")] struct HostnameResources { - option: smoltcp::wire::DhcpOption<'static>, - data: [u8; MAX_HOSTNAME_LEN], + option: MaybeUninit>, + data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>, } impl StackResources { /// Create a new set of stack resources. pub const fn new() -> Self { - #[cfg(feature = "dns")] - const INIT: Option = None; Self { - sockets: [SocketStorage::EMPTY; SOCK], + sockets: MaybeUninit::uninit(), + inner: MaybeUninit::uninit(), #[cfg(feature = "dns")] - queries: [INIT; MAX_QUERIES], + queries: MaybeUninit::uninit(), #[cfg(feature = "dhcpv4-hostname")] - hostname: core::cell::UnsafeCell::new(HostnameResources { - option: smoltcp::wire::DhcpOption { kind: 0, data: &[] }, - data: [0; MAX_HOSTNAME_LEN], - }), + hostname: HostnameResources { + option: MaybeUninit::uninit(), + data: MaybeUninit::uninit(), + }, } } } @@ -237,16 +240,32 @@ pub enum ConfigV6 { Static(StaticConfigV6), } -/// A network stack. +/// Network stack runner. /// -/// This is the main entry point for the network stack. -pub struct Stack { - pub(crate) socket: RefCell, - inner: RefCell>, +/// You must call [`Runner::run()`] in a background task for the network stack to work. +pub struct Runner<'d, D: Driver> { + driver: D, + stack: Stack<'d>, } -struct Inner { - device: D, +/// Network stack handle +/// +/// Use this to create sockets. It's `Copy`, so you can pass +/// it by value instead of by reference. +#[derive(Copy, Clone)] +pub struct Stack<'d> { + inner: &'d RefCell, +} + +pub(crate) struct Inner { + pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased. + pub(crate) iface: Interface, + /// Waker used for triggering polls. + pub(crate) waker: WakerRegistration, + /// Waker used for waiting for link up or config up. + state_waker: WakerRegistration, + hardware_address: HardwareAddress, + next_local_port: u16, link_up: bool, #[cfg(feature = "proto-ipv4")] static_v4: Option, @@ -254,20 +273,88 @@ struct Inner { static_v6: Option, #[cfg(feature = "dhcpv4")] dhcp_socket: Option, - config_waker: WakerRegistration, #[cfg(feature = "dns")] dns_socket: SocketHandle, #[cfg(feature = "dns")] dns_waker: WakerRegistration, #[cfg(feature = "dhcpv4-hostname")] - hostname: &'static mut core::cell::UnsafeCell, + hostname: *mut HostnameResources, } -pub(crate) struct SocketStack { - pub(crate) sockets: SocketSet<'static>, - pub(crate) iface: Interface, - pub(crate) waker: WakerRegistration, - next_local_port: u16, +fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> { + x +} + +/// Create a new network stack. +pub fn new<'d, D: Driver, const SOCK: usize>( + mut driver: D, + config: Config, + resources: &'d mut StackResources, + random_seed: u64, +) -> (Stack<'d>, Runner<'d, D>) { + let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address()); + let mut iface_cfg = smoltcp::iface::Config::new(hardware_address); + iface_cfg.random_seed = random_seed; + + let iface = Interface::new( + iface_cfg, + &mut DriverAdapter { + inner: &mut driver, + cx: None, + medium, + }, + instant_to_smoltcp(Instant::now()), + ); + + unsafe fn transmute_slice(x: &mut [T]) -> &'static mut [T] { + core::mem::transmute(x) + } + + let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]); + #[allow(unused_mut)] + let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) }); + + let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; + + #[cfg(feature = "dns")] + let dns_socket = sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(unsafe { + transmute_slice(resources.queries.write([const { None }; MAX_QUERIES])) + }), + )); + + let mut inner = Inner { + sockets, + iface, + waker: WakerRegistration::new(), + state_waker: WakerRegistration::new(), + next_local_port, + hardware_address, + link_up: false, + #[cfg(feature = "proto-ipv4")] + static_v4: None, + #[cfg(feature = "proto-ipv6")] + static_v6: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), + #[cfg(feature = "dhcpv4-hostname")] + hostname: &mut resources.hostname, + }; + + #[cfg(feature = "proto-ipv4")] + inner.set_config_v4(config.ipv4); + #[cfg(feature = "proto-ipv6")] + inner.set_config_v6(config.ipv6); + inner.apply_static_config(); + + let inner = &*resources.inner.write(RefCell::new(inner)); + let stack = Stack { inner }; + (stack, Runner { driver, stack }) } fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) { @@ -290,89 +377,23 @@ fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddres } } -impl Stack { - /// Create a new network stack. - pub fn new( - mut device: D, - config: Config, - resources: &'static mut StackResources, - random_seed: u64, - ) -> Self { - let (hardware_addr, medium) = to_smoltcp_hardware_address(device.hardware_address()); - let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr); - iface_cfg.random_seed = random_seed; - - let iface = Interface::new( - iface_cfg, - &mut DriverAdapter { - inner: &mut device, - cx: None, - medium, - }, - instant_to_smoltcp(Instant::now()), - ); - - let sockets = SocketSet::new(&mut resources.sockets[..]); - - let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; - - #[cfg_attr(feature = "medium-ieee802154", allow(unused_mut))] - let mut socket = SocketStack { - sockets, - iface, - waker: WakerRegistration::new(), - next_local_port, - }; - - let mut inner = Inner { - device, - link_up: false, - #[cfg(feature = "proto-ipv4")] - static_v4: None, - #[cfg(feature = "proto-ipv6")] - static_v6: None, - #[cfg(feature = "dhcpv4")] - dhcp_socket: None, - config_waker: WakerRegistration::new(), - #[cfg(feature = "dns")] - dns_socket: socket.sockets.add(dns::Socket::new( - &[], - managed::ManagedSlice::Borrowed(&mut resources.queries), - )), - #[cfg(feature = "dns")] - dns_waker: WakerRegistration::new(), - #[cfg(feature = "dhcpv4-hostname")] - hostname: &mut resources.hostname, - }; - - #[cfg(feature = "proto-ipv4")] - inner.set_config_v4(&mut socket, config.ipv4); - #[cfg(feature = "proto-ipv6")] - inner.set_config_v6(&mut socket, config.ipv6); - inner.apply_static_config(&mut socket); - - Self { - socket: RefCell::new(socket), - inner: RefCell::new(inner), - } +impl<'d> Stack<'d> { + fn with(&self, f: impl FnOnce(&Inner) -> R) -> R { + f(&*self.inner.borrow()) } - fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { - f(&*self.socket.borrow(), &*self.inner.borrow()) - } - - fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { - f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) + fn with_mut(&self, f: impl FnOnce(&mut Inner) -> R) -> R { + f(&mut *self.inner.borrow_mut()) } /// Get the hardware address of the network interface. pub fn hardware_address(&self) -> HardwareAddress { - self.with(|_s, i| to_smoltcp_hardware_address(i.device.hardware_address()).0) + self.with(|i| i.hardware_address) } /// Get whether the link is up. pub fn is_link_up(&self) -> bool { - self.with(|_s, i| i.link_up) + self.with(|i| i.link_up) } /// Get whether the network stack has a valid IP configuration. @@ -402,10 +423,20 @@ impl Stack { v4_up || v6_up } + /// Wait for the network device to obtain a link signal. + pub async fn wait_link_up(&self) { + self.wait(|| self.is_link_up()).await + } + + /// Wait for the network device to lose link signal. + pub async fn wait_link_down(&self) { + self.wait(|| !self.is_link_up()).await + } + /// Wait for the network stack to obtain a valid IP configuration. /// /// ## Notes: - /// - Ensure [`Stack::run`] has been called before using this function. + /// - Ensure [`Runner::run`] has been started before using this function. /// /// - This function may never return (e.g. if no configuration is obtained through DHCP). /// The caller is supposed to handle a timeout for this case. @@ -413,44 +444,49 @@ impl Stack { /// ## Example /// ```ignore /// let config = embassy_net::Config::dhcpv4(Default::default()); - ///// Init network stack - /// static RESOURCES: StaticCell> = StaticCell::new(); - /// static STACK: StaticCell = StaticCell::new(); - /// let stack = &*STACK.init(embassy_net::Stack::new( - /// device, + /// // Init network stack + /// // NOTE: DHCP and DNS need one socket slot if enabled. This is why we're + /// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP). + /// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it. + /// static RESOURCES: StaticCell> = StaticCell::new(); + /// let (stack, runner) = embassy_net::new( + /// driver, /// config, /// RESOURCES.init(embassy_net::StackResources::new()), /// seed - /// )); - /// // Launch network task that runs `stack.run().await` - /// spawner.spawn(net_task(stack)).unwrap(); + /// ); + /// // Launch network task that runs `runner.run().await` + /// spawner.spawn(net_task(runner)).unwrap(); /// // Wait for DHCP config /// stack.wait_config_up().await; /// // use the network stack /// // ... /// ``` pub async fn wait_config_up(&self) { - // If the config is up already, we can return immediately. - if self.is_config_up() { - return; - } + self.wait(|| self.is_config_up()).await + } - poll_fn(|cx| { - if self.is_config_up() { + /// Wait for the network stack to lose a valid IP configuration. + pub async fn wait_config_down(&self) { + self.wait(|| !self.is_config_up()).await + } + + fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future + 'a { + poll_fn(move |cx| { + if predicate() { Poll::Ready(()) } else { // If the config is not up, we register a waker that is woken up // when a config is applied (static or DHCP). trace!("Waiting for config up"); - self.with_mut(|_, i| { - i.config_waker.register(cx.waker()); + self.with_mut(|i| { + i.state_waker.register(cx.waker()); }); Poll::Pending } }) - .await; } /// Get the current IPv4 configuration. @@ -459,45 +495,33 @@ impl Stack { /// acquire an IP address, or Some if it has. #[cfg(feature = "proto-ipv4")] pub fn config_v4(&self) -> Option { - self.with(|_, i| i.static_v4.clone()) + self.with(|i| i.static_v4.clone()) } /// Get the current IPv6 configuration. #[cfg(feature = "proto-ipv6")] pub fn config_v6(&self) -> Option { - self.with(|_, i| i.static_v6.clone()) + self.with(|i| i.static_v6.clone()) } /// Set the IPv4 configuration. #[cfg(feature = "proto-ipv4")] pub fn set_config_v4(&self, config: ConfigV4) { - self.with_mut(|s, i| { - i.set_config_v4(s, config); - i.apply_static_config(s); + self.with_mut(|i| { + i.set_config_v4(config); + i.apply_static_config(); }) } /// Set the IPv6 configuration. #[cfg(feature = "proto-ipv6")] pub fn set_config_v6(&self, config: ConfigV6) { - self.with_mut(|s, i| { - i.set_config_v6(s, config); - i.apply_static_config(s); + self.with_mut(|i| { + i.set_config_v6(config); + i.apply_static_config(); }) } - /// Run the network stack. - /// - /// You must call this in a background task, to process network events. - pub async fn run(&self) -> ! { - poll_fn(|cx| { - self.with_mut(|s, i| i.poll(cx, s)); - Poll::<()>::Pending - }) - .await; - unreachable!() - } - /// Make a query for a given name and return the corresponding IP addresses. #[cfg(feature = "dns")] pub async fn dns_query( @@ -523,11 +547,11 @@ impl Stack { } let query = poll_fn(|cx| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); - match socket.start_query(s.iface.context(), name, qtype) { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + match socket.start_query(i.iface.context(), name, qtype) { Ok(handle) => { - s.waker.wake(); + i.waker.wake(); Poll::Ready(Ok(handle)) } Err(dns::StartQueryError::NoFreeSlot) => { @@ -564,17 +588,17 @@ impl Stack { } let drop = OnDrop::new(|| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); socket.cancel_query(query); - s.waker.wake(); + i.waker.wake(); i.dns_waker.wake(); }) }); let res = poll_fn(|cx| { - self.with_mut(|s, i| { - let socket = s.sockets.get_mut::(i.dns_socket); + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); match socket.get_query_result(query) { Ok(addrs) => { i.dns_waker.wake(); @@ -599,104 +623,34 @@ impl Stack { } } -#[cfg(feature = "igmp")] -impl Stack { +#[cfg(feature = "multicast")] +impl<'d> Stack<'d> { /// Join a multicast group. - pub async fn join_multicast_group(&self, addr: T) -> Result - where - T: Into, - { - let addr = addr.into(); - - poll_fn(move |cx| self.poll_join_multicast_group(addr, cx)).await - } - - /// Join a multicast group. - /// - /// When the send queue is full, this method will return `Poll::Pending` - /// and register the current task to be notified when the queue has space available. - pub fn poll_join_multicast_group(&self, addr: T, cx: &mut Context<'_>) -> Poll> - where - T: Into, - { - let addr = addr.into(); - - self.with_mut(|s, i| { - let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address()); - let mut smoldev = DriverAdapter { - cx: Some(cx), - inner: &mut i.device, - medium, - }; - - match s - .iface - .join_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now())) - { - Ok(announce_sent) => Poll::Ready(Ok(announce_sent)), - Err(MulticastError::Exhausted) => Poll::Pending, - Err(other) => Poll::Ready(Err(other)), - } - }) + pub fn join_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.join_multicast_group(addr)) } /// Leave a multicast group. - pub async fn leave_multicast_group(&self, addr: T) -> Result - where - T: Into, - { - let addr = addr.into(); - - poll_fn(move |cx| self.poll_leave_multicast_group(addr, cx)).await - } - - /// Leave a multicast group. - /// - /// When the send queue is full, this method will return `Poll::Pending` - /// and register the current task to be notified when the queue has space available. - pub fn poll_leave_multicast_group(&self, addr: T, cx: &mut Context<'_>) -> Poll> - where - T: Into, - { - let addr = addr.into(); - - self.with_mut(|s, i| { - let (_hardware_addr, medium) = to_smoltcp_hardware_address(i.device.hardware_address()); - let mut smoldev = DriverAdapter { - cx: Some(cx), - inner: &mut i.device, - medium, - }; - - match s - .iface - .leave_multicast_group(&mut smoldev, addr, instant_to_smoltcp(Instant::now())) - { - Ok(leave_sent) => Poll::Ready(Ok(leave_sent)), - Err(MulticastError::Exhausted) => Poll::Pending, - Err(other) => Poll::Ready(Err(other)), - } - }) + pub fn leave_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.leave_multicast_group(addr)) } /// Get whether the network stack has joined the given multicast group. - pub fn has_multicast_group>(&self, addr: T) -> bool { - self.socket.borrow().iface.has_multicast_group(addr) + pub fn has_multicast_group(&self, addr: impl Into) -> bool { + self.with(|i| i.iface.has_multicast_group(addr)) } } -impl SocketStack { +impl Inner { #[allow(clippy::absurd_extreme_comparisons, dead_code)] pub fn get_local_port(&mut self) -> u16 { let res = self.next_local_port; self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; res } -} -impl Inner { #[cfg(feature = "proto-ipv4")] - pub fn set_config_v4(&mut self, _s: &mut SocketStack, config: ConfigV4) { + pub fn set_config_v4(&mut self, config: ConfigV4) { // Handle static config. self.static_v4 = match config.clone() { ConfigV4::None => None, @@ -712,12 +666,12 @@ impl Inner { // Create the socket if it doesn't exist. if self.dhcp_socket.is_none() { let socket = smoltcp::socket::dhcpv4::Socket::new(); - let handle = _s.sockets.add(socket); + let handle = self.sockets.add(socket); self.dhcp_socket = Some(handle); } // Configure it - let socket = _s.sockets.get_mut::(unwrap!(self.dhcp_socket)); + let socket = self.sockets.get_mut::(unwrap!(self.dhcp_socket)); socket.set_ignore_naks(c.ignore_naks); socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp)); socket.set_ports(c.server_port, c.client_port); @@ -726,19 +680,20 @@ impl Inner { socket.set_outgoing_options(&[]); #[cfg(feature = "dhcpv4-hostname")] if let Some(h) = c.hostname { - // safety: we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. - let hostname = unsafe { &mut *self.hostname.get() }; + // safety: + // - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. + // - we know this pointer lives for as long as the stack exists, because `new()` borrows + // the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp. + let hostname = unsafe { &mut *self.hostname }; // create data - // safety: we know the buffer lives forever, new borrows the StackResources for 'static. - // also we won't modify it until next call to this function. - hostname.data[..h.len()].copy_from_slice(h.as_bytes()); - let data: &[u8] = &hostname.data[..h.len()]; - let data: &'static [u8] = unsafe { core::mem::transmute(data) }; + let data = hostname.data.write([0; MAX_HOSTNAME_LEN]); + data[..h.len()].copy_from_slice(h.as_bytes()); + let data: &[u8] = &data[..h.len()]; // set the option. - hostname.option = smoltcp::wire::DhcpOption { data, kind: 12 }; - socket.set_outgoing_options(core::slice::from_ref(&hostname.option)); + let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 }); + socket.set_outgoing_options(core::slice::from_ref(option)); } socket.reset(); @@ -746,7 +701,7 @@ impl Inner { _ => { // Remove DHCP socket if any. if let Some(socket) = self.dhcp_socket { - _s.sockets.remove(socket); + self.sockets.remove(socket); self.dhcp_socket = None; } } @@ -754,14 +709,14 @@ impl Inner { } #[cfg(feature = "proto-ipv6")] - pub fn set_config_v6(&mut self, _s: &mut SocketStack, config: ConfigV6) { + pub fn set_config_v6(&mut self, config: ConfigV6) { self.static_v6 = match config { ConfigV6::None => None, ConfigV6::Static(c) => Some(c), }; } - fn apply_static_config(&mut self, s: &mut SocketStack) { + fn apply_static_config(&mut self) { let mut addrs = Vec::new(); #[cfg(feature = "dns")] let mut dns_servers: Vec<_, 6> = Vec::new(); @@ -805,35 +760,43 @@ impl Inner { } // Apply addresses - s.iface.update_ip_addrs(|a| *a = addrs); + self.iface.update_ip_addrs(|a| *a = addrs); // Apply gateways #[cfg(feature = "proto-ipv4")] if let Some(gateway) = gateway_v4 { - unwrap!(s.iface.routes_mut().add_default_ipv4_route(gateway)); + unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway)); } else { - s.iface.routes_mut().remove_default_ipv4_route(); + self.iface.routes_mut().remove_default_ipv4_route(); } #[cfg(feature = "proto-ipv6")] if let Some(gateway) = gateway_v6 { - unwrap!(s.iface.routes_mut().add_default_ipv6_route(gateway)); + unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway)); } else { - s.iface.routes_mut().remove_default_ipv6_route(); + self.iface.routes_mut().remove_default_ipv6_route(); } // Apply DNS servers #[cfg(feature = "dns")] - s.sockets - .get_mut::(self.dns_socket) - .update_servers(&dns_servers[..]); + if !dns_servers.is_empty() { + let count = if dns_servers.len() > DNS_MAX_SERVER_COUNT { + warn!("Number of DNS servers exceeds DNS_MAX_SERVER_COUNT, truncating list."); + DNS_MAX_SERVER_COUNT + } else { + dns_servers.len() + }; + self.sockets + .get_mut::(self.dns_socket) + .update_servers(&dns_servers[..count]); + } - self.config_waker.wake(); + self.state_waker.wake(); } - fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { - s.waker.register(cx.waker()); + fn poll(&mut self, cx: &mut Context<'_>, driver: &mut D) { + self.waker.register(cx.waker()); - let (_hardware_addr, medium) = to_smoltcp_hardware_address(self.device.hardware_address()); + let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address()); #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] { @@ -846,25 +809,26 @@ impl Inner { _ => false, }; if do_set { - s.iface.set_hardware_addr(_hardware_addr); + self.iface.set_hardware_addr(_hardware_addr); } } let timestamp = instant_to_smoltcp(Instant::now()); let mut smoldev = DriverAdapter { cx: Some(cx), - inner: &mut self.device, + inner: driver, medium, }; - s.iface.poll(timestamp, &mut smoldev, &mut s.sockets); + self.iface.poll(timestamp, &mut smoldev, &mut self.sockets); // Update link up let old_link_up = self.link_up; - self.link_up = self.device.link_state(cx) == LinkState::Up; + self.link_up = driver.link_state(cx) == LinkState::Up; // Print when changed if old_link_up != self.link_up { info!("link_up = {:?}", self.link_up); + self.state_waker.wake(); } #[allow(unused_mut)] @@ -872,7 +836,7 @@ impl Inner { #[cfg(feature = "dhcpv4")] if let Some(dhcp_handle) = self.dhcp_socket { - let socket = s.sockets.get_mut::(dhcp_handle); + let socket = self.sockets.get_mut::(dhcp_handle); if self.link_up { if old_link_up != self.link_up { @@ -901,10 +865,10 @@ impl Inner { } if apply_config { - self.apply_static_config(s); + self.apply_static_config(); } - if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { + if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) { let t = pin!(Timer::at(instant_from_smoltcp(poll_at))); if t.poll(cx).is_ready() { cx.waker().wake_by_ref(); @@ -912,3 +876,17 @@ impl Inner { } } } + +impl<'d, D: Driver> Runner<'d, D> { + /// Run the network stack. + /// + /// You must call this in a background task, to process network events. + pub async fn run(&mut self) -> ! { + poll_fn(|cx| { + self.stack.with_mut(|i| i.poll(cx, &mut self.driver)); + Poll::<()>::Pending + }) + .await; + unreachable!() + } +} diff --git a/embassy-net/src/raw.rs b/embassy-net/src/raw.rs index 7ecd913e7..1098dc208 100644 --- a/embassy-net/src/raw.rs +++ b/embassy-net/src/raw.rs @@ -1,6 +1,5 @@ //! Raw sockets. -use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::{Context, Poll}; @@ -11,7 +10,7 @@ use smoltcp::socket::raw; pub use smoltcp::socket::raw::PacketMetadata; use smoltcp::wire::{IpProtocol, IpVersion}; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by [`RawSocket::recv`] and [`RawSocket::send`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -23,14 +22,14 @@ pub enum RecvError { /// An Raw socket. pub struct RawSocket<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'a> RawSocket<'a> { /// Create a new Raw socket using the provided stack and buffers. pub fn new( - stack: &'a Stack, + stack: Stack<'a>, ip_version: IpVersion, ip_protocol: IpProtocol, rx_meta: &'a mut [PacketMetadata], @@ -38,31 +37,29 @@ impl<'a> RawSocket<'a> { tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - let s = &mut *stack.socket.borrow_mut(); + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(raw::Socket::new( + ip_version, + ip_protocol, + raw::PacketBuffer::new(rx_meta, rx_buffer), + raw::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); - let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(raw::Socket::new( - ip_version, - ip_protocol, - raw::PacketBuffer::new(rx_meta, rx_buffer), - raw::PacketBuffer::new(tx_meta, tx_buffer), - )); - - Self { - stack: &stack.socket, - handle, - } + Self { stack, handle } } fn with_mut(&self, f: impl FnOnce(&mut raw::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) } /// Receive a datagram. @@ -115,6 +112,10 @@ impl<'a> RawSocket<'a> { impl Drop for RawSocket<'_> { fn drop(&mut self) { - self.stack.borrow_mut().sockets.remove(self.handle); + self.stack.with_mut(|i| i.sockets.remove(self.handle)); } } + +fn _assert_covariant<'a, 'b: 'a>(x: RawSocket<'b>) -> RawSocket<'a> { + x +} diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index be0e1a129..bcddbc95b 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -8,12 +8,10 @@ //! Incoming connections when no socket is listening are rejected. To accept many incoming //! connections, create many sockets and put them all into listening mode. -use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::Poll; -use embassy_net_driver::Driver; use embassy_time::Duration; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::tcp; @@ -21,7 +19,7 @@ pub use smoltcp::socket::tcp::State; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; use crate::time::duration_to_smoltcp; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by TcpSocket read/write functions. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -79,6 +77,15 @@ impl<'a> TcpReader<'a> { /// /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. + /// + /// # Note + /// A return value of Ok(0) means that we have read all data and the remote + /// side has closed our receive half of the socket. The remote can no longer + /// send bytes. + /// + /// The send half of the socket is still open. If you want to reconnect using + /// the socket you split this reader off the send half needs to be closed using + /// [`abort()`](TcpSocket::abort). pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } @@ -148,20 +155,18 @@ impl<'a> TcpWriter<'a> { impl<'a> TcpSocket<'a> { /// Create a new TCP socket on the given stack, with the given buffers. - pub fn new(stack: &'a Stack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { - let s = &mut *stack.socket.borrow_mut(); - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(tcp::Socket::new( - tcp::SocketBuffer::new(rx_buffer), - tcp::SocketBuffer::new(tx_buffer), - )); + pub fn new(stack: Stack<'a>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { + let handle = stack.with_mut(|i| { + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(tcp::Socket::new( + tcp::SocketBuffer::new(rx_buffer), + tcp::SocketBuffer::new(tx_buffer), + )) + }); Self { - io: TcpIo { - stack: &stack.socket, - handle, - }, + io: TcpIo { stack: stack, handle }, } } @@ -219,7 +224,7 @@ impl<'a> TcpSocket<'a> { where T: Into, { - let local_port = self.io.stack.borrow_mut().get_local_port(); + let local_port = self.io.stack.with_mut(|i| i.get_local_port()); match { self.io @@ -273,6 +278,9 @@ impl<'a> TcpSocket<'a> { /// /// Returns how many bytes were read, or an error. If no data is available, it waits /// until there is at least one byte available. + /// + /// A return value of Ok(0) means that the socket was closed and is longer + /// able to receive any data. pub async fn read(&mut self, buf: &mut [u8]) -> Result { self.io.read(buf).await } @@ -297,6 +305,10 @@ impl<'a> TcpSocket<'a> { /// /// If the timeout is set, the socket will be closed if no data is received for the /// specified duration. + /// + /// # Note: + /// Set a keep alive interval ([`set_keep_alive`] to prevent timeouts when + /// the remote could still respond. pub fn set_timeout(&mut self, duration: Option) { self.io .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) @@ -308,6 +320,9 @@ impl<'a> TcpSocket<'a> { /// the specified duration of inactivity. /// /// If not set, the socket will not send keep-alive packets. + /// + /// By setting a [`timeout`](Self::timeout) larger then the keep alive you + /// can detect a remote endpoint that no longer answers. pub fn set_keep_alive(&mut self, interval: Option) { self.io .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) @@ -382,31 +397,43 @@ impl<'a> TcpSocket<'a> { impl<'a> Drop for TcpSocket<'a> { fn drop(&mut self) { - self.io.stack.borrow_mut().sockets.remove(self.io.handle); + self.io.stack.with_mut(|i| i.sockets.remove(self.io.handle)); } } +fn _assert_covariant<'a, 'b: 'a>(x: TcpSocket<'b>) -> TcpSocket<'a> { + x +} +fn _assert_covariant_reader<'a, 'b: 'a>(x: TcpReader<'b>) -> TcpReader<'a> { + x +} +fn _assert_covariant_writer<'a, 'b: 'a>(x: TcpWriter<'b>) -> TcpWriter<'a> { + x +} + // ======================= #[derive(Copy, Clone)] struct TcpIo<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'d> TcpIo<'d> { fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.borrow(); - let socket = s.sockets.get::(self.handle); - f(socket, &s.iface) + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) } fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) } async fn read(&mut self, buf: &mut [u8]) -> Result { @@ -515,7 +542,7 @@ impl<'d> TcpIo<'d> { async fn flush(&mut self) -> Result<(), Error> { poll_fn(move |cx| { self.with_mut(|s, _| { - let data_pending = s.send_queue() > 0; + let data_pending = (s.send_queue() > 0) && s.state() != tcp::State::Closed; let fin_pending = matches!( s.state(), tcp::State::FinWait1 | tcp::State::Closing | tcp::State::LastAck @@ -657,15 +684,15 @@ pub mod client { /// TCP client connection pool compatible with `embedded-nal-async` traits. /// /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. - pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { - stack: &'d Stack, + pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + stack: Stack<'d>, state: &'d TcpClientState, socket_timeout: Option, } - impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> { /// Create a new `TcpClient`. - pub fn new(stack: &'d Stack, state: &'d TcpClientState) -> Self { + pub fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Self { Self { stack, state, @@ -682,8 +709,8 @@ pub mod client { } } - impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect - for TcpClient<'d, D, N, TX_SZ, RX_SZ> + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + for TcpClient<'d, N, TX_SZ, RX_SZ> { type Error = Error; type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; @@ -703,7 +730,7 @@ pub mod client { IpAddr::V6(_) => panic!("ipv6 support not enabled"), }; let remote_endpoint = (addr, remote.port()); - let mut socket = TcpConnection::new(&self.stack, self.state)?; + let mut socket = TcpConnection::new(self.stack, self.state)?; socket.socket.set_timeout(self.socket_timeout.clone()); socket .socket @@ -722,7 +749,7 @@ pub mod client { } impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> { - fn new(stack: &'d Stack, state: &'d TcpClientState) -> Result { + fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Result { let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; Ok(Self { socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 6e50c4e01..3eb6e2f83 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -1,17 +1,15 @@ //! UDP sockets. -use core::cell::RefCell; use core::future::poll_fn; use core::mem; use core::task::{Context, Poll}; -use embassy_net_driver::Driver; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::udp; pub use smoltcp::socket::udp::{PacketMetadata, UdpMetadata}; use smoltcp::wire::IpListenEndpoint; -use crate::{SocketStack, Stack}; +use crate::Stack; /// Error returned by [`UdpSocket::bind`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] @@ -43,34 +41,31 @@ pub enum RecvError { /// An UDP socket. pub struct UdpSocket<'a> { - stack: &'a RefCell, + stack: Stack<'a>, handle: SocketHandle, } impl<'a> UdpSocket<'a> { /// Create a new UDP socket using the provided stack and buffers. - pub fn new( - stack: &'a Stack, + pub fn new( + stack: Stack<'a>, rx_meta: &'a mut [PacketMetadata], rx_buffer: &'a mut [u8], tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - let s = &mut *stack.socket.borrow_mut(); + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(udp::Socket::new( + udp::PacketBuffer::new(rx_meta, rx_buffer), + udp::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); - let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; - let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; - let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; - let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; - let handle = s.sockets.add(udp::Socket::new( - udp::PacketBuffer::new(rx_meta, rx_buffer), - udp::PacketBuffer::new(tx_meta, tx_buffer), - )); - - Self { - stack: &stack.socket, - handle, - } + Self { stack, handle } } /// Bind the socket to a local endpoint. @@ -82,7 +77,7 @@ impl<'a> UdpSocket<'a> { if endpoint.port == 0 { // If user didn't specify port allocate a dynamic port. - endpoint.port = self.stack.borrow_mut().get_local_port(); + endpoint.port = self.stack.with_mut(|i| i.get_local_port()); } match self.with_mut(|s, _| s.bind(endpoint)) { @@ -93,17 +88,19 @@ impl<'a> UdpSocket<'a> { } fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.borrow(); - let socket = s.sockets.get::(self.handle); - f(socket, &s.iface) + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) } fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.borrow_mut(); - let socket = s.sockets.get_mut::(self.handle); - let res = f(socket, &mut s.iface); - s.waker.wake(); - res + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) } /// Receive a datagram. @@ -138,6 +135,35 @@ impl<'a> UdpSocket<'a> { }) } + /// Receive a datagram with a zero-copy function. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will call the provided function + /// with the number of bytes received and the remote endpoint and return + /// `Poll::Ready` with the function's returned value. + pub async fn recv_from_with(&mut self, f: F) -> R + where + F: FnOnce(&[u8], UdpMetadata) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.recv() { + Ok((buffer, endpoint)) => Poll::Ready(unwrap!(f.take())(buffer, endpoint)), + Err(udp::RecvError::Truncated) => unreachable!(), + Err(udp::RecvError::Exhausted) => { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + } + }) + }) + .await + } + /// Send a datagram to the specified remote endpoint. /// /// This method will wait until the datagram has been sent. @@ -181,6 +207,40 @@ impl<'a> UdpSocket<'a> { }) } + /// Send a datagram to the specified remote endpoint with a zero-copy function. + /// + /// This method will wait until the buffer can fit the requested size before + /// calling the function to fill its contents. + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to_with(&mut self, size: usize, remote_endpoint: T, f: F) -> Result + where + T: Into + Copy, + F: FnOnce(&mut [u8]) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.send(size, remote_endpoint) { + Ok(buffer) => Poll::Ready(Ok(unwrap!(f.take())(buffer))), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.endpoint().port == 0 { + Poll::Ready(Err(SendError::SocketNotBound)) + } else { + Poll::Ready(Err(SendError::NoRoute)) + } + } + } + }) + }) + .await + } + /// Returns the local endpoint of the socket. pub fn endpoint(&self) -> IpListenEndpoint { self.with(|s, _| s.endpoint()) @@ -235,6 +295,10 @@ impl<'a> UdpSocket<'a> { impl Drop for UdpSocket<'_> { fn drop(&mut self) { - self.stack.borrow_mut().sockets.remove(self.handle); + self.stack.with_mut(|i| i.sockets.remove(self.handle)); } } + +fn _assert_covariant<'a, 'b: 'a>(x: UdpSocket<'b>) -> UdpSocket<'a> { + x +} diff --git a/embassy-nrf/CHANGELOG.md b/embassy-nrf/CHANGELOG.md index 6f07a8c6d..f8d6ab753 100644 --- a/embassy-nrf/CHANGELOG.md +++ b/embassy-nrf/CHANGELOG.md @@ -7,24 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- Drop `sealed` mod -- nrf52840: Add dcdc voltage parameter to configure REG0 regulator -- radio: Add support for IEEE 802.15.4 and BLE via radio peripheral -- spim: Reduce trace-level messages ("Copying SPIM tx buffer..") -- uart: Add support for rx- or tx-only BufferedUart -- uart: Implement splitting Rx/Tx -- spi: Allow specifying OutputDrive for SPI spins -- pdm: Fix gain register value derivation -- spim: Implement chunked DMA transfers -- spi: Add bounds checks for EasyDMA buffer size -- uarte: Add support for handling RX errors -- nrf51: Implement support for nrf51 chip -- pwm: Expose `duty` method -- pwm: Fix infinite loop -- spi: Add support for configuring bit order for bus -- pwm: Expose `pwm::PWM_CLK_HZ` and add `is_enabled` method -- gpio: Drop GPIO Pin generics (API break) -- pwm: Allow specifying OutputDrive for PWM channels +## 0.2.0 - 2024-08-05 + +- Support for NRF chips: + - nrf51 + - nrf9151 +- Support for new peripherals: + - EGU + - radio - low-level support for IEEE 802.15.4 and BLE via radio peripheral +- Peripheral changes: + - gpio: Drop GPIO Pin generics (API break) + - pdm: Fix gain register value derivation + - pwm: + - Expose `duty` method + - Expose `pwm::PWM_CLK_HZ` and add `is_enabled` method + - Allow specifying OutputDrive for PWM channels + - Fix infinite loop + - spim: + - Reduce trace-level messages ("Copying SPIM tx buffer..") + - Support configuring bit order for bus + - Allow specifying OutputDrive for SPI pins + - Add bounds checks for EasyDMA buffer size + - Implement chunked DMA transfers + - uart: + - Add support for rx- or tx-only BufferedUart + - Implement splitting Rx/Tx + - Add support for handling RX errors +- Miscellaneous changes: + - Add `collapse_debuginfo` to fmt.rs macros. + - Drop `sealed` mod + - nrf52840: Add dcdc voltage parameter to configure REG0 regulator ## 0.1.0 - 2024-01-12 diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index f47b724ce..3e66d6886 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-nrf" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for nRF series microcontrollers" @@ -103,12 +103,12 @@ nrf9160-ns = ["_nrf9160", "_ns", "_nrf91"] ## The nRF9120 is the internal part number for the nRF9161 and nRF9151. ## nRF9120 in Secure mode nrf9120-s = ["_nrf9120", "_s", "_nrf91"] -nrf9151-s = ["_nrf9120", "_s", "_nrf91"] -nrf9161-s = ["_nrf9120", "_s", "_nrf91"] +nrf9151-s = ["nrf9120-s"] +nrf9161-s = ["nrf9120-s"] ## nRF9120 in Non-Secure mode nrf9120-ns = ["_nrf9120", "_ns", "_nrf91"] -nrf9151-ns = ["_nrf9120", "_ns", "_nrf91"] -nrf9161-ns = ["_nrf9120", "_ns", "_nrf91"] +nrf9151-ns = ["nrf9120-ns"] +nrf9161-ns = ["nrf9120-ns"] # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. @@ -137,10 +137,10 @@ _nrf52832_anomaly_109 = [] [dependencies] embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } -embassy-time = { version = "0.3.1", path = "../embassy-time", optional = true } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } -embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-3"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index b368a3d33..6d39597c6 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -219,6 +219,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { /// # Panics /// /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] pub fn new( uarte: impl Peripheral

+ 'd, timer: impl Peripheral

+ 'd, @@ -254,6 +255,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { /// # Panics /// /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] pub fn new_with_rtscts( uarte: impl Peripheral

+ 'd, timer: impl Peripheral

+ 'd, @@ -286,6 +288,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { ) } + #[allow(clippy::too_many_arguments)] fn new_inner( peri: PeripheralRef<'d, U>, timer: PeripheralRef<'d, T>, @@ -355,6 +358,11 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { self.tx.write(buf).await } + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + self.tx.try_write(buf) + } + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. pub async fn flush(&mut self) -> Result<(), Error> { self.tx.flush().await @@ -479,6 +487,29 @@ impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { .await } + /// Try writing a buffer without waiting, returning how many bytes were written. + pub fn try_write(&mut self, buf: &[u8]) -> Result { + //trace!("poll_write: {:?}", buf.len()); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + return Ok(0); + } + + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + U::Interrupt::pend(); + + Ok(n) + } + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. pub async fn flush(&mut self) -> Result<(), Error> { poll_fn(move |cx| { @@ -534,6 +565,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { /// # Panics /// /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] pub fn new( uarte: impl Peripheral

+ 'd, timer: impl Peripheral

+ 'd, @@ -564,6 +596,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { /// # Panics /// /// Panics if `rx_buffer.len()` is odd. + #[allow(clippy::too_many_arguments)] pub fn new_with_rts( uarte: impl Peripheral

+ 'd, timer: impl Peripheral

+ 'd, @@ -590,6 +623,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { ) } + #[allow(clippy::too_many_arguments)] fn new_inner( peri: PeripheralRef<'d, U>, timer: PeripheralRef<'d, T>, @@ -614,6 +648,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'d, U, T> { this } + #[allow(clippy::too_many_arguments)] fn new_innerer( peri: PeripheralRef<'d, U>, timer: PeripheralRef<'d, T>, diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index 7b272dca0..dbc26ea3f 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -534,11 +534,13 @@ mod eh02 { type Error = Infallible; fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(self.set_high()) + self.set_high(); + Ok(()) } fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(self.set_low()) + self.set_low(); + Ok(()) } } @@ -580,11 +582,13 @@ mod eh02 { type Error = Infallible; fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(self.set_high()) + self.set_high(); + Ok(()) } fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(self.set_low()) + self.set_low(); + Ok(()) } } @@ -628,11 +632,13 @@ impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(self.set_high()) + self.set_high(); + Ok(()) } fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(self.set_low()) + self.set_low(); + Ok(()) } } @@ -665,11 +671,13 @@ impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { fn set_high(&mut self) -> Result<(), Self::Error> { - Ok(self.set_high()) + self.set_high(); + Ok(()) } fn set_low(&mut self) -> Result<(), Self::Error> { - Ok(self.set_low()) + self.set_low(); + Ok(()) } } diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index a3a5b9cfe..bbfa9b3b9 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -78,7 +78,8 @@ impl Default for Config { /// Used to configure an individual SAADC peripheral channel. /// -/// See the `Default` impl for suitable default values. +/// Construct using the `single_ended` or `differential` methods. These provide sensible defaults +/// for the public fields, which can be overridden if required. #[non_exhaustive] pub struct ChannelConfig<'d> { /// Reference voltage of the SAADC input. diff --git a/embassy-nrf/src/wdt.rs b/embassy-nrf/src/wdt.rs index 5a261ce8f..e4cfa3344 100644 --- a/embassy-nrf/src/wdt.rs +++ b/embassy-nrf/src/wdt.rs @@ -184,8 +184,9 @@ impl WatchdogHandle { /// Steal a watchdog handle by index. /// - /// Safety: watchdog must be initialized, index must be between 0 and N-1 where - /// N is the handle count when initializing. + /// # Safety + /// Watchdog must be initialized and `index` must be between `0` and `N-1` + /// where `N` is the handle count when initializing. pub unsafe fn steal(index: u8) -> Self { Self { index } } diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md new file mode 100644 index 000000000..7eef64292 --- /dev/null +++ b/embassy-rp/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog for embassy-rp + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Unreleased + +## 0.2.0 - 2024-08-05 + +- Add read_to_break_with_count +- add option to provide your own boot2 +- Add multichannel ADC +- Add collapse_debuginfo to fmt.rs macros. +- Use raw slices .len() method instead of unsafe hacks. +- Add missing word "pin" in rp pwm documentation +- Add Clone and Copy to Error types +- fix spinlocks staying locked after reset. +- wait until read matches for PSM accesses. +- Remove generics +- fix drop implementation of BufferedUartRx and BufferedUartTx +- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash` +- rp usb: wake ep-wakers after stalling +- rp usb: add stall implementation +- Add parameter for enabling pull-up and pull-down in RP PWM input mode +- rp: remove mod sealed. +- rename pins data type and the macro +- rename pwm channels to pwm slices, including in documentation +- rename the Channel trait to Slice and the PwmPin to PwmChannel +- i2c: Fix race condition that appears on fast repeated transfers. +- Add a basic "read to break" function diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index 447c96b4d..29a8a3c53 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-rp" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 microcontroller" @@ -14,7 +14,9 @@ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/emba src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/" features = ["defmt", "unstable-pac", "time-driver"] flavors = [ - { name = "rp2040", target = "thumbv6m-none-eabi" }, + { name = "rp2040", target = "thumbv6m-none-eabi", features = ["rp2040"] }, + { name = "rp235xa", target = "thumbv8m.main-none-eabihf", features = ["rp235xa"] }, + { name = "rp235xb", target = "thumbv8m.main-none-eabihf", features = ["rp235xb"] }, ] [package.metadata.docs.rs] @@ -22,13 +24,13 @@ features = ["defmt", "unstable-pac", "time-driver"] [features] default = [ "rt" ] -## Enable the rt feature of [`rp-pac`](https://docs.rs/crates/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). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization. rt = [ "rp-pac/rt" ] ## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. defmt = ["dep:defmt", "embassy-usb-driver/defmt", "embassy-hal-internal/defmt"] -## Configure the critical section crate to use an implementation that is safe for multicore use on rp2040. +## Configure the [`critical-section`](https://docs.rs/critical-section) crate to use an implementation that is safe for multicore use on rp2040. critical-section-impl = ["critical-section/restore-state-u8"] ## Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. @@ -88,13 +90,30 @@ boot2-w25x10cl = [] ## ``` boot2-none = [] +## Configure the hal for use with the rp2040 +rp2040 = ["rp-pac/rp2040"] +_rp235x = ["rp-pac/rp235x"] +## Configure the hal for use with the rp235xA +rp235xa = ["_rp235x"] +## Configure the hal for use with the rp235xB +rp235xb = ["_rp235x"] + +# hack around cortex-m peripherals being wrong when running tests. +_test = [] + +## Add a binary-info header block containing picotool-compatible metadata. +## +## Takes up a little flash space, but picotool can then report the name of your +## program and other details. +binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"] + [dependencies] embassy-sync = { version = "0.6.0", path = "../embassy-sync" } embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } -embassy-time = { version = "0.3.1", path = "../embassy-time" } +embassy-time = { version = "0.3.2", path = "../embassy-time" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } -embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } atomic-polyfill = "1.0.1" defmt = { version = "0.3", optional = true } @@ -112,7 +131,7 @@ embedded-storage-async = { version = "0.4.1" } rand_core = "0.6.4" fixed = "1.23.1" -rp-pac = { version = "6" } +rp-pac = { git = "https://github.com/embassy-rs/rp-pac.git", rev = "a7f42d25517f7124ad3b4ed492dec8b0f50a0e6c", feature = ["rt"] } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } @@ -123,7 +142,9 @@ pio-proc = {version= "0.2" } pio = {version= "0.2.1" } rp2040-boot2 = "0.3" document-features = "0.2.7" +sha2-const-stable = "0.1" +rp-binary-info = { version = "0.1.0", optional = true } [dev-dependencies] -embassy-executor = { version = "0.5.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +embassy-executor = { version = "0.6.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } static_cell = { version = "2" } diff --git a/embassy-rp/LICENSE-APACHE b/embassy-rp/LICENSE-APACHE new file mode 100644 index 000000000..48be1263d --- /dev/null +++ b/embassy-rp/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embassy-rp/LICENSE-MIT b/embassy-rp/LICENSE-MIT new file mode 100644 index 000000000..f1cfbd5f7 --- /dev/null +++ b/embassy-rp/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embassy-rp/README.md b/embassy-rp/README.md index 8cf7da994..16b189344 100644 --- a/embassy-rp/README.md +++ b/embassy-rp/README.md @@ -23,5 +23,5 @@ The `embassy-rp` HAL implements the traits from [embedded-hal](https://crates.io This crate can run on any executor. -Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time` feature. If you enable it, +Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time-driver` feature. If you enable it, you must link an `embassy-time` driver in your project. diff --git a/embassy-rp/build.rs b/embassy-rp/build.rs index f41ccd220..3216a3826 100644 --- a/embassy-rp/build.rs +++ b/embassy-rp/build.rs @@ -4,14 +4,16 @@ use std::io::Write; use std::path::PathBuf; fn main() { - // Put the linker script somewhere the linker can find it - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - let link_x = include_bytes!("link-rp.x.in"); - let mut f = File::create(out.join("link-rp.x")).unwrap(); - f.write_all(link_x).unwrap(); + if env::var("CARGO_FEATURE_RP2040").is_ok() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let link_x = include_bytes!("link-rp.x.in"); + let mut f = File::create(out.join("link-rp.x")).unwrap(); + f.write_all(link_x).unwrap(); - println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rustc-link-search={}", out.display()); - println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-changed=link-rp.x.in"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=link-rp.x.in"); + } } diff --git a/embassy-rp/link-rp.x.in b/embassy-rp/link-rp.x.in index af463f963..1839dda68 100644 --- a/embassy-rp/link-rp.x.in +++ b/embassy-rp/link-rp.x.in @@ -5,4 +5,4 @@ SECTIONS { { KEEP(*(.boot2)); } > BOOT2 -} \ No newline at end of file +} diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs index eb1cc9a66..9582e43c8 100644 --- a/embassy-rp/src/adc.rs +++ b/embassy-rp/src/adc.rs @@ -11,6 +11,7 @@ use embassy_sync::waitqueue::AtomicWaker; use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin}; use crate::interrupt::typelevel::Binding; use crate::interrupt::InterruptExt; +use crate::pac::dma::vals::TreqSel; use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt}; @@ -34,6 +35,8 @@ impl<'p> Channel<'p> { pub fn new_pin(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { into_ref!(pin); pin.pad_ctrl().modify(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); // manual says: // // > When using an ADC input shared with a GPIO pin, the pin’s @@ -229,7 +232,10 @@ impl<'d> Adc<'d, Async> { div: u16, dma: impl Peripheral

, ) -> Result<(), Error> { + #[cfg(feature = "rp2040")] let mut rrobin = 0_u8; + #[cfg(feature = "_rp235x")] + let mut rrobin = 0_u16; for c in channels { rrobin |= 1 << c; } @@ -278,7 +284,7 @@ impl<'d> Adc<'d, Async> { } let auto_reset = ResetDmaConfig; - let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], 36) }; + let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], TreqSel::ADC) }; // start conversions and wait for dma to finish. we can't report errors early // because there's no interrupt to signal them, and inspecting every element // of the fifo is too costly to do here. @@ -423,10 +429,31 @@ macro_rules! impl_pin { }; } +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] impl_pin!(PIN_26, 0); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] impl_pin!(PIN_27, 1); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] impl_pin!(PIN_28, 2); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] impl_pin!(PIN_29, 3); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, 0); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, 1); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, 2); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, 3); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, 4); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, 5); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, 6); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, 7); + impl SealedAdcChannel for peripherals::ADC_TEMP_SENSOR {} impl AdcChannel for peripherals::ADC_TEMP_SENSOR {} diff --git a/embassy-rp/src/block.rs b/embassy-rp/src/block.rs new file mode 100644 index 000000000..a3e1ad925 --- /dev/null +++ b/embassy-rp/src/block.rs @@ -0,0 +1,1079 @@ +//! Support for the RP235x Boot ROM's "Block" structures +//! +//! Blocks contain pointers, to form Block Loops. +//! +//! The `IMAGE_DEF` Block (here the `ImageDef` type) tells the ROM how to boot a +//! firmware image. The `PARTITION_TABLE` Block (here the `PartitionTable` type) +//! tells the ROM how to divide the flash space up into partitions. + +// Credit: Taken from https://github.com/thejpster/rp-hal-rp2350-public (also licensed Apache 2.0 + MIT). +// Copyright (c) rp-rs organization + +// These all have a 1 byte size + +/// An item ID for encoding a Vector Table address +pub const ITEM_1BS_VECTOR_TABLE: u8 = 0x03; + +/// An item ID for encoding a Rolling Window Delta +pub const ITEM_1BS_ROLLING_WINDOW_DELTA: u8 = 0x05; + +/// An item ID for encoding a Signature +pub const ITEM_1BS_SIGNATURE: u8 = 0x09; + +/// An item ID for encoding a Salt +pub const ITEM_1BS_SALT: u8 = 0x0c; + +/// An item ID for encoding an Image Type +pub const ITEM_1BS_IMAGE_TYPE: u8 = 0x42; + +/// An item ID for encoding the image's Entry Point +pub const ITEM_1BS_ENTRY_POINT: u8 = 0x44; + +/// An item ID for encoding the definition of a Hash +pub const ITEM_2BS_HASH_DEF: u8 = 0x47; + +/// An item ID for encoding a Version +pub const ITEM_1BS_VERSION: u8 = 0x48; + +/// An item ID for encoding a Hash +pub const ITEM_1BS_HASH_VALUE: u8 = 0x4b; + +// These all have a 2-byte size + +/// An item ID for encoding a Load Map +pub const ITEM_2BS_LOAD_MAP: u8 = 0x06; + +/// An item ID for encoding a Partition Table +pub const ITEM_2BS_PARTITION_TABLE: u8 = 0x0a; + +/// An item ID for encoding a placeholder entry that is ignored +/// +/// Allows a Block to not be empty. +pub const ITEM_2BS_IGNORED: u8 = 0xfe; + +/// An item ID for encoding the special last item in a Block +/// +/// It records how long the Block is. +pub const ITEM_2BS_LAST: u8 = 0xff; + +// Options for ITEM_1BS_IMAGE_TYPE + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as invalid +pub const IMAGE_TYPE_INVALID: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as an executable +pub const IMAGE_TYPE_EXE: u16 = 0x0001; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as data +pub const IMAGE_TYPE_DATA: u16 = 0x0002; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as unspecified +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_NS: u16 = 0x0010; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_S: u16 = 0x0020; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as Arm +pub const IMAGE_TYPE_EXE_CPU_ARM: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as RISC-V +pub const IMAGE_TYPE_EXE_CPU_RISCV: u16 = 0x0100; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2040 +pub const IMAGE_TYPE_EXE_CHIP_RP2040: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2350 +pub const IMAGE_TYPE_EXE_CHIP_RP2350: u16 = 0x1000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the image as Try Before You Buy. +/// +/// This means the image must be marked as 'Bought' with the ROM before the +/// watchdog times out the trial period, otherwise it is erased and the previous +/// image will be booted. +pub const IMAGE_TYPE_TBYB: u16 = 0x8000; + +/// This is the magic Block Start value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_START` +const BLOCK_MARKER_START: u32 = 0xffffded3; + +/// This is the magic Block END value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_END` +const BLOCK_MARKER_END: u32 = 0xab123579; + +/// An Image Definition has one item in it - an [`ITEM_1BS_IMAGE_TYPE`] +pub type ImageDef = Block<1>; + +/// A Block as understood by the Boot ROM. +/// +/// This could be an Image Definition, or a Partition Table, or maybe some other +/// kind of block. +/// +/// It contains within the special start and end markers the Boot ROM is looking +/// for. +#[derive(Debug)] +#[repr(C)] +pub struct Block { + marker_start: u32, + items: [u32; N], + length: u32, + offset: *const u32, + marker_end: u32, +} + +unsafe impl Sync for Block {} + +impl Block { + /// Construct a new Binary Block, with the given items. + /// + /// The length, and the Start and End markers are added automatically. The + /// Block Loop pointer initially points to itself. + pub const fn new(items: [u32; N]) -> Block { + Block { + marker_start: BLOCK_MARKER_START, + items, + length: item_last(N as u16), + // offset from this block to next block in loop. By default + // we form a Block Loop with a single Block in it. + offset: core::ptr::null(), + marker_end: BLOCK_MARKER_END, + } + } + + /// Change the Block Loop offset value. + /// + /// This method isn't that useful because you can't evaluate the difference + /// between two pointers in a const context as the addresses aren't assigned + /// until long after the const evaluator has run. + /// + /// If you think you need this method, you might want to set a unique random + /// value here and swap it for the real offset as a post-processing step. + pub const fn with_offset(self, offset: *const u32) -> Block { + Block { offset, ..self } + } +} + +impl Block<0> { + /// Construct an empty block. + pub const fn empty() -> Block<0> { + Block::new([]) + } + + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<1> { + Block::new([word]) + } +} + +impl Block<1> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<2> { + Block::new([self.items[0], word]) + } +} + +impl Block<2> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<3> { + Block::new([self.items[0], self.items[1], word]) + } +} + +impl ImageDef { + /// Construct a new IMAGE_DEF Block, for an EXE with the given security and + /// architecture. + pub const fn arch_exe(security: Security, architecture: Architecture) -> Self { + Self::new([item_image_type_exe(security, architecture)]) + } + + /// Construct a new IMAGE_DEF Block, for an EXE with the given security. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn exe(security: Security) -> Self { + if cfg!(all(target_arch = "riscv32", target_os = "none")) { + Self::arch_exe(security, Architecture::Riscv) + } else { + Self::arch_exe(security, Architecture::Arm) + } + } + + /// Construct a new IMAGE_DEF Block, for a Non-Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn non_secure_exe() -> Self { + Self::exe(Security::NonSecure) + } + + /// Construct a new IMAGE_DEF Block, for a Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn secure_exe() -> Self { + Self::exe(Security::Secure) + } +} + +/// We make our partition table this fixed size. +pub const PARTITION_TABLE_MAX_ITEMS: usize = 128; + +/// Describes a unpartitioned space +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct UnpartitionedSpace { + permissions_and_location: u32, + permissions_and_flags: u32, +} + +impl UnpartitionedSpace { + /// Create a new unpartitioned space. + /// + /// It defaults to no permissions. + pub const fn new() -> Self { + Self { + permissions_and_location: 0, + permissions_and_flags: 0, + } + } + + /// Create a new unpartition space from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PT_INFO`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | permission as u32, + permissions_and_location: self.permissions_and_location | permission as u32, + } + } + + /// Set a flag + pub const fn with_flag(self, flag: UnpartitionedFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: UnpartitionedFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for UnpartitionedSpace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a Partition +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Partition { + permissions_and_location: u32, + permissions_and_flags: u32, + id: Option, + extra_families: [u32; 4], + extra_families_len: usize, + name: [u8; 128], +} + +impl Partition { + const FLAGS_HAS_ID: u32 = 0b1; + const FLAGS_LINK_TYPE_A_PARTITION: u32 = 0b01 << 1; + const FLAGS_LINK_TYPE_OWNER: u32 = 0b10 << 1; + const FLAGS_LINK_MASK: u32 = 0b111111 << 1; + const FLAGS_HAS_NAME: u32 = 0b1 << 12; + const FLAGS_HAS_EXTRA_FAMILIES_SHIFT: u8 = 7; + const FLAGS_HAS_EXTRA_FAMILIES_MASK: u32 = 0b11 << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT; + + /// Create a new partition, with the given start and end sectors. + /// + /// It defaults to no permissions. + pub const fn new(first_sector: u16, last_sector: u16) -> Self { + // 0x2000 sectors of 4 KiB is 32 MiB, which is the total XIP area + core::assert!(first_sector < 0x2000); + core::assert!(last_sector < 0x2000); + core::assert!(first_sector <= last_sector); + Self { + permissions_and_location: (last_sector as u32) << 13 | first_sector as u32, + permissions_and_flags: 0, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Create a new partition from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PARTITION_LOCATION_AND_FLAGS`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_location: self.permissions_and_location | permission as u32, + permissions_and_flags: self.permissions_and_flags | permission as u32, + ..self + } + } + + /// Set the name of the partition + pub const fn with_name(self, name: &str) -> Self { + let mut new_name = [0u8; 128]; + let name = name.as_bytes(); + let mut idx = 0; + new_name[0] = name.len() as u8; + while idx < name.len() { + new_name[idx + 1] = name[idx]; + idx += 1; + } + Self { + name: new_name, + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_NAME, + ..self + } + } + + /// Set the extra families for the partition. + /// + /// You can supply up to four. + pub const fn with_extra_families(self, extra_families: &[u32]) -> Self { + core::assert!(extra_families.len() <= 4); + let mut new_extra_families = [0u32; 4]; + let mut idx = 0; + while idx < extra_families.len() { + new_extra_families[idx] = extra_families[idx]; + idx += 1; + } + Self { + extra_families: new_extra_families, + extra_families_len: extra_families.len(), + permissions_and_flags: (self.permissions_and_flags & !Self::FLAGS_HAS_EXTRA_FAMILIES_MASK) + | (extra_families.len() as u32) << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT, + ..self + } + } + + /// Set the ID + pub const fn with_id(self, id: u64) -> Self { + Self { + id: Some(id), + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_ID, + ..self + } + } + + /// Add a link + pub const fn with_link(self, link: Link) -> Self { + let mut new_flags = self.permissions_and_flags & !Self::FLAGS_LINK_MASK; + match link { + Link::Nothing => {} + Link::ToA { partition_idx } => { + core::assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_A_PARTITION; + new_flags |= (partition_idx as u32) << 3; + } + Link::ToOwner { partition_idx } => { + core::assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_OWNER; + new_flags |= (partition_idx as u32) << 3; + } + } + Self { + permissions_and_flags: new_flags, + ..self + } + } + + /// Set a flag + pub const fn with_flag(self, flag: PartitionFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Get which extra families are allowed in this partition + pub fn get_extra_families(&self) -> &[u32] { + &self.extra_families[0..self.extra_families_len] + } + + /// Get the name of the partition + /// + /// Returns `None` if there's no name, or the name is not valid UTF-8. + pub fn get_name(&self) -> Option<&str> { + let len = self.name[0] as usize; + if len == 0 { + None + } else { + core::str::from_utf8(&self.name[1..=len]).ok() + } + } + + /// Get the ID + pub fn get_id(&self) -> Option { + self.id + } + + /// Check if this partition is linked + pub fn get_link(&self) -> Link { + if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_A_PARTITION) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToA { partition_idx } + } else if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_OWNER) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToOwner { partition_idx } + } else { + Link::Nothing + } + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: PartitionFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for Partition { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a partition table. +/// +/// Don't store this as a static - make sure you convert it to a block. +#[derive(Clone)] +pub struct PartitionTableBlock { + /// This must look like a block, including the 1 word header and the 3 word footer. + contents: [u32; PARTITION_TABLE_MAX_ITEMS], + /// This value doesn't include the 1 word header or the 3 word footer + num_items: usize, +} + +impl PartitionTableBlock { + /// Create an empty Block, big enough for a partition table. + /// + /// At a minimum you need to call [`Self::add_partition_item`]. + pub const fn new() -> PartitionTableBlock { + let mut contents = [0; PARTITION_TABLE_MAX_ITEMS]; + contents[0] = BLOCK_MARKER_START; + contents[1] = item_last(0); + contents[2] = 0; + contents[3] = BLOCK_MARKER_END; + PartitionTableBlock { contents, num_items: 0 } + } + + /// Add a partition to the partition table + pub const fn add_partition_item(self, unpartitioned: UnpartitionedSpace, partitions: &[Partition]) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item header space (we fill this in later) + let header_idx = idx; + new_table.contents[idx] = 0; + idx += 1; + + // 2. unpartitioned space flags + // + // (the location of unpartition space is not recorded here - it is + // inferred because the unpartitioned space is where the partitions are + // not) + new_table.contents[idx] = unpartitioned.permissions_and_flags; + idx += 1; + + // 3. partition info + + let mut partition_no = 0; + while partition_no < partitions.len() { + // a. permissions_and_location (4K units) + new_table.contents[idx] = partitions[partition_no].permissions_and_location; + idx += 1; + + // b. permissions_and_flags + new_table.contents[idx] = partitions[partition_no].permissions_and_flags; + idx += 1; + + // c. ID + if let Some(id) = partitions[partition_no].id { + new_table.contents[idx] = id as u32; + new_table.contents[idx + 1] = (id >> 32) as u32; + idx += 2; + } + + // d. Extra Families + let mut extra_families_idx = 0; + while extra_families_idx < partitions[partition_no].extra_families_len { + new_table.contents[idx] = partitions[partition_no].extra_families[extra_families_idx]; + idx += 1; + extra_families_idx += 1; + } + + // e. Name + let mut name_idx = 0; + while name_idx < partitions[partition_no].name[0] as usize { + let name_chunk = [ + partitions[partition_no].name[name_idx], + partitions[partition_no].name[name_idx + 1], + partitions[partition_no].name[name_idx + 2], + partitions[partition_no].name[name_idx + 3], + ]; + new_table.contents[idx] = u32::from_le_bytes(name_chunk); + name_idx += 4; + idx += 1; + } + + partition_no += 1; + } + + let len = idx - header_idx; + new_table.contents[header_idx] = item_generic_2bs(partitions.len() as u8, len as u16, ITEM_2BS_PARTITION_TABLE); + + // 7. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a version number to the partition table + pub const fn with_version(self, major: u16, minor: u16) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item + new_table.contents[idx] = item_generic_2bs(0, 2, ITEM_1BS_VERSION); + idx += 1; + new_table.contents[idx] = (major as u32) << 16 | minor as u32; + idx += 1; + + // 2. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a a SHA256 hash of the Block + /// + /// Adds a `HASH_DEF` covering all the previous items in the Block, and a + /// `HASH_VALUE` with a SHA-256 hash of them. + pub const fn with_sha256(self) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. HASH_DEF says what is hashed + new_table.contents[idx] = item_generic_2bs(1, 2, ITEM_2BS_HASH_DEF); + idx += 1; + // we're hashing all the previous contents - including this line. + new_table.contents[idx] = (idx + 1) as u32; + idx += 1; + + // calculate hash over prior contents + let input = unsafe { core::slice::from_raw_parts(new_table.contents.as_ptr() as *const u8, idx * 4) }; + let hash: [u8; 32] = sha2_const_stable::Sha256::new().update(input).finalize(); + + // 2. HASH_VALUE contains the hash + new_table.contents[idx] = item_generic_2bs(0, 9, ITEM_1BS_HASH_VALUE); + idx += 1; + + let mut hash_idx = 0; + while hash_idx < hash.len() { + new_table.contents[idx] = u32::from_le_bytes([ + hash[hash_idx], + hash[hash_idx + 1], + hash[hash_idx + 2], + hash[hash_idx + 3], + ]); + idx += 1; + hash_idx += 4; + } + + // 3. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } +} + +impl Default for PartitionTableBlock { + fn default() -> Self { + Self::new() + } +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum PartitionFlag { + NotBootableArm = 1 << 9, + NotBootableRiscv = 1 << 10, + Uf2DownloadAbNonBootableOwnerAffinity = 1 << 11, + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum UnpartitionedFlag { + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyAbsolute = 1 << 15, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Kinds of linked partition +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Link { + /// Not linked to anything + Nothing, + /// This is a B partition - link to our A partition. + ToA { + /// The index of our matching A partition. + partition_idx: u8, + }, + /// Link to the partition that owns this one. + ToOwner { + /// The idx of our owner + partition_idx: u8, + }, +} + +/// Permissions that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum Permission { + /// Can be read in Secure Mode + /// + /// Corresponds to `PERMISSION_S_R_BITS` in the Pico SDK + SecureRead = 1 << 26, + /// Can be written in Secure Mode + /// + /// Corresponds to `PERMISSION_S_W_BITS` in the Pico SDK + SecureWrite = 1 << 27, + /// Can be read in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_R_BITS` in the Pico SDK + NonSecureRead = 1 << 28, + /// Can be written in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_W_BITS` in the Pico SDK + NonSecureWrite = 1 << 29, + /// Can be read in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_R_BITS` in the Pico SDK + BootRead = 1 << 30, + /// Can be written in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_W_BITS` in the Pico SDK + BootWrite = 1 << 31, +} + +impl Permission { + /// Is this permission bit set this in this bitmask? + pub const fn is_in(self, mask: u32) -> bool { + (mask & (self as u32)) != 0 + } +} + +/// The supported RP2350 CPU architectures +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Architecture { + /// Core is in Arm Cortex-M33 mode + Arm, + /// Core is in RISC-V / Hazard3 mode + Riscv, +} + +/// The kinds of Secure Boot we support +#[derive(Debug, Copy, Clone)] +pub enum Security { + /// Security mode not given + Unspecified, + /// Start in Non-Secure mode + NonSecure, + /// Start in Secure mode + Secure, +} + +/// Make an item containing a tag, 1 byte length and two extra bytes. +/// +/// The `command` arg should contain `1BS` +pub const fn item_generic_1bs(value: u16, length: u8, command: u8) -> u32 { + ((value as u32) << 16) | ((length as u32) << 8) | (command as u32) +} + +/// Make an item containing a tag, 2 byte length and one extra byte. +/// +/// The `command` arg should contain `2BS` +pub const fn item_generic_2bs(value: u8, length: u16, command: u8) -> u32 { + ((value as u32) << 24) | ((length as u32) << 8) | (command as u32) +} + +/// Create Image Type item, of type IGNORED. +pub const fn item_ignored() -> u32 { + item_generic_2bs(0, 1, ITEM_2BS_IGNORED) +} + +/// Create Image Type item, of type INVALID. +pub const fn item_image_type_invalid() -> u32 { + let value = IMAGE_TYPE_INVALID; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type DATA. +pub const fn item_image_type_data() -> u32 { + let value = IMAGE_TYPE_DATA; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type EXE. +pub const fn item_image_type_exe(security: Security, arch: Architecture) -> u32 { + let mut value = IMAGE_TYPE_EXE | IMAGE_TYPE_EXE_CHIP_RP2350; + + match arch { + Architecture::Arm => { + value |= IMAGE_TYPE_EXE_CPU_ARM; + } + Architecture::Riscv => { + value |= IMAGE_TYPE_EXE_CPU_RISCV; + } + } + + match security { + Security::Unspecified => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED, + Security::NonSecure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_NS, + Security::Secure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_S, + } + + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create a Block Last item. +pub const fn item_last(length: u16) -> u32 { + item_generic_2bs(0, length, ITEM_2BS_LAST) +} + +/// Create a Vector Table item. +/// +/// This is only allowed on Arm systems. +pub const fn item_vector_table(table_ptr: u32) -> [u32; 2] { + [item_generic_1bs(0, 2, ITEM_1BS_VECTOR_TABLE), table_ptr] +} + +/// Create an Entry Point item. +pub const fn item_entry_point(entry_point: u32, initial_sp: u32) -> [u32; 3] { + [item_generic_1bs(0, 3, ITEM_1BS_ENTRY_POINT), entry_point, initial_sp] +} + +/// Create an Rolling Window item. +/// +/// The delta is the number of bytes into the image that 0x10000000 should +/// be mapped. +pub const fn item_rolling_window(delta: u32) -> [u32; 2] { + [item_generic_1bs(0, 3, ITEM_1BS_ROLLING_WINDOW_DELTA), delta] +} + +#[cfg(test)] +mod test { + use super::*; + + /// I used this JSON, with `picotool partition create`: + /// + /// ```json + /// { + /// "version": [1, 0], + /// "unpartitioned": { + /// "families": ["absolute"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// "partitions": [ + /// { + /// "name": "A", + /// "id": 0, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// { + /// "name": "B", + /// "id": 1, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// }, + /// "link": ["a", 0] + /// } + /// ] + /// } + /// ``` + #[test] + fn make_hashed_partition_table() { + let table = PartitionTableBlock::new() + .add_partition_item( + UnpartitionedSpace::new() + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_flag(UnpartitionedFlag::AcceptsDefaultFamilyAbsolute), + &[ + Partition::new(2, 512) + .with_id(0) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("A"), + Partition::new(513, 1023) + .with_id(1) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_link(Link::ToA { partition_idx: 0 }) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("B"), + ], + ) + .with_version(1, 0) + .with_sha256(); + let expected = &[ + 0xffffded3, // start + 0x02000c0a, // Item = PARTITION_TABLE + 0xfc008000, // Unpartitioned Space - permissions_and_flags + 0xfc400002, // Partition 0 - permissions_and_location (512 * 4096, 2 * 4096) + 0xfc061001, // permissions_and_flags HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000000, // ID + 0x00000000, // ID + 0x00004101, // Name ("A") + 0xfc7fe201, // Partition 1 - permissions_and_location (1023 * 4096, 513 * 4096) + 0xfc061003, // permissions_and_flags LINKA(0) | HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000001, // ID + 0x00000000, // ID + 0x00004201, // Name ("B") + 0x00000248, // Item = Version + 0x00010000, // 0, 1 + 0x01000247, // HASH_DEF with 2 words, and SHA256 hash + 0x00000011, // 17 words hashed + 0x0000094b, // HASH_VALUE with 9 words + 0x1945cdad, // Hash word 0 + 0x6b5f9773, // Hash word 1 + 0xe2bf39bd, // Hash word 2 + 0xb243e599, // Hash word 3 + 0xab2f0e9a, // Hash word 4 + 0x4d5d6d0b, // Hash word 5 + 0xf973050f, // Hash word 6 + 0x5ab6dadb, // Hash word 7 + 0x000019ff, // Last Item + 0x00000000, // Block Loop Next Offset + 0xab123579, // End + ]; + core::assert_eq!( + &table.contents[..29], + expected, + "{:#010x?}\n != \n{:#010x?}", + &table.contents[0..29], + expected, + ); + } +} diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index d0c6c19bd..f229a5acd 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -1,12 +1,17 @@ //! Clock configuration for the RP2040 + +#[cfg(feature = "rp2040")] use core::arch::asm; use core::marker::PhantomData; -use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; +#[cfg(feature = "rp2040")] +use core::sync::atomic::AtomicU16; +use core::sync::atomic::{AtomicU32, Ordering}; use embassy_hal_internal::{into_ref, PeripheralRef}; use pac::clocks::vals::*; use crate::gpio::{AnyPin, SealedPin}; +#[cfg(feature = "rp2040")] use crate::pac::common::{Reg, RW}; use crate::{pac, reset, Peripheral}; @@ -26,6 +31,7 @@ struct Clocks { // gpin1: AtomicU32, rosc: AtomicU32, peri: AtomicU32, + #[cfg(feature = "rp2040")] rtc: AtomicU16, } @@ -41,6 +47,7 @@ static CLOCKS: Clocks = Clocks { // gpin1: AtomicU32::new(0), rosc: AtomicU32::new(0), peri: AtomicU32::new(0), + #[cfg(feature = "rp2040")] rtc: AtomicU16::new(0), }; @@ -81,6 +88,7 @@ pub struct ClockConfig { /// ADC clock configuration. pub adc_clk: Option, /// RTC clock configuration. + #[cfg(feature = "rp2040")] pub rtc_clk: Option, // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, @@ -135,6 +143,7 @@ impl ClockConfig { phase: 0, }), // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz + #[cfg(feature = "rp2040")] rtc_clk: Some(RtcClkConfig { src: RtcClkSrc::PllUsb, div_int: 1024, @@ -174,6 +183,7 @@ impl ClockConfig { phase: 0, }), // CLK RTC = ROSC (140MHz) / 2986.667969 ≅ 46875Hz + #[cfg(feature = "rp2040")] rtc_clk: Some(RtcClkConfig { src: RtcClkSrc::Rosc, div_int: 2986, @@ -295,9 +305,17 @@ pub struct SysClkConfig { /// SYS clock source. pub src: SysClkSrc, /// SYS clock divider. + #[cfg(feature = "rp2040")] pub div_int: u32, /// SYS clock fraction. + #[cfg(feature = "rp2040")] pub div_frac: u8, + /// SYS clock divider. + #[cfg(feature = "_rp235x")] + pub div_int: u16, + /// SYS clock fraction. + #[cfg(feature = "_rp235x")] + pub div_frac: u16, } /// USB clock source. @@ -358,6 +376,7 @@ pub struct AdcClkConfig { #[repr(u8)] #[non_exhaustive] #[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg(feature = "rp2040")] pub enum RtcClkSrc { /// PLL USB. PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB as _, @@ -372,6 +391,7 @@ pub enum RtcClkSrc { } /// RTC clock config. +#[cfg(feature = "rp2040")] pub struct RtcClkConfig { /// RTC clock source. pub src: RtcClkSrc, @@ -396,10 +416,9 @@ pub(crate) unsafe fn init(config: ClockConfig) { peris.set_pads_qspi(false); peris.set_pll_sys(false); peris.set_pll_usb(false); - // TODO investigate if usb should be unreset here peris.set_usbctrl(false); peris.set_syscfg(false); - peris.set_rtc(false); + //peris.set_rtc(false); reset::reset(peris); // Disable resus that may be enabled from previous software @@ -409,9 +428,15 @@ pub(crate) unsafe fn init(config: ClockConfig) { // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); + #[cfg(feature = "rp2040")] while c.clk_sys_selected().read() != 1 {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1) {} c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH)); + #[cfg(feature = "rp2040")] while c.clk_ref_selected().read() != 1 {} + #[cfg(feature = "_rp235x")] + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} // Reset the PLLs let mut peris = reset::Peripherals(0); @@ -479,15 +504,26 @@ pub(crate) unsafe fn init(config: ClockConfig) { w.set_src(ref_src); w.set_auxsrc(ref_aux); }); - while c.clk_ref_selected().read() != 1 << ref_src as u32 {} + #[cfg(feature = "rp2040")] + while c.clk_ref_selected().read() != (1 << ref_src as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ref_src as u32) {} c.clk_ref_div().write(|w| { w.set_int(config.ref_clk.div); }); + // Configure tick generation on the 2040. + #[cfg(feature = "rp2040")] pac::WATCHDOG.tick().write(|w| { w.set_cycles((clk_ref_freq / 1_000_000) as u16); w.set_enable(true); }); + // Configure tick generator on the 2350 + #[cfg(feature = "_rp235x")] + { + pac::TICKS.timer0_cycles().write(|w| w.0 = clk_ref_freq / 1_000_000); + pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true)); + } let (sys_src, sys_aux, clk_sys_freq) = { use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; @@ -500,7 +536,6 @@ pub(crate) unsafe fn init(config: ClockConfig) { // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), }; - assert!(config.sys_clk.div_int <= 0x1000000); let div = config.sys_clk.div_int as u64 * 256 + config.sys_clk.div_frac as u64; (src, aux, ((freq as u64 * 256) / div) as u32) }; @@ -508,13 +543,21 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); if sys_src != ClkSysCtrlSrc::CLK_REF { c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); - while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF as u32 {} + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLK_REF as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLK_REF as u32) {} } c.clk_sys_ctrl().write(|w| { w.set_auxsrc(sys_aux); w.set_src(sys_src); }); - while c.clk_sys_selected().read() != 1 << sys_src as u32 {} + + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << sys_src as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {} + c.clk_sys_div().write(|w| { w.set_int(config.sys_clk.div_int); w.set_frac(config.sys_clk.div_frac); @@ -592,6 +635,8 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.adc.store(0, Ordering::Relaxed); } + // rp2040 specific clocks + #[cfg(feature = "rp2040")] if let Some(conf) = config.rtc_clk { c.clk_rtc_div().write(|w| { w.set_int(conf.div_int); @@ -621,6 +666,13 @@ pub(crate) unsafe fn init(config: ClockConfig) { CLOCKS.rtc.store(0, Ordering::Relaxed); } + // rp235x specific clocks + #[cfg(feature = "_rp235x")] + { + // TODO hstx clock + peris.set_hstx(false); + } + // Peripheral clocks should now all be running reset::unreset_wait(peris); } @@ -709,6 +761,7 @@ pub fn clk_adc_freq() -> u32 { } /// RTC clock frequency. +#[cfg(feature = "rp2040")] pub fn clk_rtc_freq() -> u16 { CLOCKS.rtc.load(Ordering::Relaxed) } @@ -794,6 +847,10 @@ impl<'d, T: GpinPin> Gpin<'d, T> { into_ref!(gpin); gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpin.pad_ctrl().write(|w| { + w.set_iso(false); + }); Gpin { gpin: gpin.map_into(), @@ -808,6 +865,7 @@ impl<'d, T: GpinPin> Gpin<'d, T> { impl<'d, T: GpinPin> Drop for Gpin<'d, T> { fn drop(&mut self) { + self.gpin.pad_ctrl().write(|_| {}); self.gpin .gpio() .ctrl() @@ -856,6 +914,7 @@ pub enum GpoutSrc { /// ADC. Adc = ClkGpoutCtrlAuxsrc::CLK_ADC as _, /// RTC. + #[cfg(feature = "rp2040")] Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC as _, /// REF. Ref = ClkGpoutCtrlAuxsrc::CLK_REF as _, @@ -867,16 +926,21 @@ pub struct Gpout<'d, T: GpoutPin> { } impl<'d, T: GpoutPin> Gpout<'d, T> { - /// Create new general purpose cloud output. + /// Create new general purpose clock output. pub fn new(gpout: impl Peripheral

+ 'd) -> Self { into_ref!(gpout); gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpout.pad_ctrl().write(|w| { + w.set_iso(false); + }); Self { gpout } } /// Set clock divider. + #[cfg(feature = "rp2040")] pub fn set_div(&self, int: u32, frac: u8) { let c = pac::CLOCKS; c.clk_gpout_div(self.gpout.number()).write(|w| { @@ -885,6 +949,16 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { }); } + /// Set clock divider. + #[cfg(feature = "_rp235x")] + pub fn set_div(&self, int: u16, frac: u16) { + let c = pac::CLOCKS; + c.clk_gpout_div(self.gpout.number()).write(|w| { + w.set_int(int); + w.set_frac(frac); + }); + } + /// Set clock source. pub fn set_src(&self, src: GpoutSrc) { let c = pac::CLOCKS; @@ -924,13 +998,13 @@ 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_RTC => clk_rtc_freq() as _, ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), _ => unreachable!(), }; let div = c.clk_gpout_div(self.gpout.number()).read(); - let int = if div.int() == 0 { 65536 } else { div.int() } as u64; + let int = if div.int() == 0 { 0xFFFF } else { div.int() } as u64; let frac = div.frac() as u64; ((base as u64 * 256) / (int * 256 + frac)) as u32 @@ -940,6 +1014,7 @@ impl<'d, T: GpoutPin> Gpout<'d, T> { impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { fn drop(&mut self) { self.disable(); + self.gpout.pad_ctrl().write(|_| {}); self.gpout .gpio() .ctrl() @@ -987,7 +1062,7 @@ impl rand_core::RngCore for RoscRng { /// and can only be exited through resets, dormant-wake GPIO interrupts, /// and RTC interrupts. If RTC is clocked from an internal clock source /// it will be stopped and not function as a wakeup source. -#[cfg(target_arch = "arm")] +#[cfg(all(target_arch = "arm", feature = "rp2040"))] pub fn dormant_sleep() { struct Set(Reg, T, F); @@ -1107,7 +1182,7 @@ pub fn dormant_sleep() { coma = in (reg) 0x636f6d61, ); } else { - pac::ROSC.dormant().write_value(0x636f6d61); + pac::ROSC.dormant().write_value(rp_pac::rosc::regs::Dormant(0x636f6d61)); } } } diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index 8c04b43a1..34abe3e2d 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs @@ -15,7 +15,7 @@ use crate::{interrupt, pac, peripherals}; #[cfg(feature = "rt")] #[interrupt] fn DMA_IRQ_0() { - let ints0 = pac::DMA.ints0().read().ints0(); + let ints0 = pac::DMA.ints(0).read(); for channel in 0..CHANNEL_COUNT { let ctrl_trig = pac::DMA.ch(channel).ctrl_trig().read(); if ctrl_trig.ahb_error() { @@ -26,14 +26,14 @@ fn DMA_IRQ_0() { CHANNEL_WAKERS[channel].wake(); } } - pac::DMA.ints0().write(|w| w.set_ints0(ints0)); + pac::DMA.ints(0).write_value(ints0); } pub(crate) unsafe fn init() { interrupt::DMA_IRQ_0.disable(); interrupt::DMA_IRQ_0.set_priority(interrupt::Priority::P3); - pac::DMA.inte0().write(|w| w.set_inte0(0xFFFF)); + pac::DMA.inte(0).write_value(0xFFFF); interrupt::DMA_IRQ_0.enable(); } @@ -45,7 +45,7 @@ pub unsafe fn read<'a, C: Channel, W: Word>( ch: impl Peripheral

+ 'a, from: *const W, to: *mut [W], - dreq: u8, + dreq: vals::TreqSel, ) -> Transfer<'a, C> { copy_inner( ch, @@ -66,7 +66,7 @@ pub unsafe fn write<'a, C: Channel, W: Word>( ch: impl Peripheral

+ 'a, from: *const [W], to: *mut W, - dreq: u8, + dreq: vals::TreqSel, ) -> Transfer<'a, C> { copy_inner( ch, @@ -90,7 +90,7 @@ pub unsafe fn write_repeated<'a, C: Channel, W: Word>( ch: impl Peripheral

+ 'a, to: *mut W, len: usize, - dreq: u8, + dreq: vals::TreqSel, ) -> Transfer<'a, C> { copy_inner( ch, @@ -123,7 +123,7 @@ pub unsafe fn copy<'a, C: Channel, W: Word>( W::size(), true, true, - vals::TreqSel::PERMANENT.0, + vals::TreqSel::PERMANENT, ) } @@ -135,7 +135,7 @@ fn copy_inner<'a, C: Channel>( data_size: DataSize, incr_read: bool, incr_write: bool, - dreq: u8, + dreq: vals::TreqSel, ) -> Transfer<'a, C> { into_ref!(ch); @@ -143,14 +143,20 @@ fn copy_inner<'a, C: Channel>( p.read_addr().write_value(from as u32); p.write_addr().write_value(to as u32); - p.trans_count().write_value(len as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| { + *w = len as u32; + }); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| { + w.set_mode(0.into()); + w.set_count(len as u32); + }); compiler_fence(Ordering::SeqCst); p.ctrl_trig().write(|w| { - // TODO: Add all DREQ options to pac vals::TreqSel, and use - // `set_treq:sel` - w.0 = ((dreq as u32) & 0x3f) << 15usize; + w.set_treq_sel(dreq); w.set_data_size(data_size); w.set_incr_read(incr_read); w.set_incr_write(incr_write); @@ -202,7 +208,10 @@ impl<'a, C: Channel> Future for Transfer<'a, C> { } } +#[cfg(feature = "rp2040")] pub(crate) const CHANNEL_COUNT: usize = 12; +#[cfg(feature = "_rp235x")] +pub(crate) const CHANNEL_COUNT: usize = 16; const NEW_AW: AtomicWaker = AtomicWaker::new(); static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; @@ -297,3 +306,11 @@ channel!(DMA_CH8, 8); channel!(DMA_CH9, 9); channel!(DMA_CH10, 10); channel!(DMA_CH11, 11); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH12, 12); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH13, 13); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH14, 14); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH15, 15); diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs index 9e4542b2f..fbc8b35ec 100644 --- a/embassy-rp/src/flash.rs +++ b/embassy-rp/src/flash.rs @@ -17,9 +17,13 @@ use crate::peripherals::FLASH; /// Flash base address. pub const FLASH_BASE: *const u32 = 0x10000000 as _; +/// Address for xip setup function set up by the 235x bootrom. +#[cfg(feature = "_rp235x")] +pub const BOOTROM_BASE: *const u32 = 0x400e0000 as _; + /// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead. // TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance. -pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram"); +pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x"); // **NOTE**: // @@ -97,7 +101,10 @@ impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, ' // Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in // flight that might effect an address written to start a new transfer. This stalls // until after any transfer is complete, so the address will not change anymore. + #[cfg(feature = "rp2040")] const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _; unsafe { core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE); } @@ -225,12 +232,14 @@ impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SI } /// Read SPI flash unique ID + #[cfg(feature = "rp2040")] pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? }; Ok(()) } /// Read SPI flash JEDEC ID + #[cfg(feature = "rp2040")] pub fn blocking_jedec_id(&mut self) -> Result { let mut jedec = None; unsafe { @@ -301,8 +310,18 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> { // Use the XIP AUX bus port, rather than the FIFO register access (e.x. // pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on // general XIP access. + #[cfg(feature = "rp2040")] const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _; - let transfer = unsafe { crate::dma::read(self.dma.as_mut().unwrap(), XIP_AUX_BASE, data, 37) }; + #[cfg(feature = "_rp235x")] + const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _; + let transfer = unsafe { + crate::dma::read( + self.dma.as_mut().unwrap(), + XIP_AUX_BASE, + data, + pac::dma::vals::TreqSel::XIP_STREAM, + ) + }; Ok(BackgroundRead { flash: PhantomData, @@ -505,7 +524,10 @@ mod ram_helpers { pub unsafe fn flash_range_erase(addr: u32, len: u32) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); flash_function_pointers_with_boot2(true, false, &boot2) } else { flash_function_pointers(true, false) @@ -535,7 +557,10 @@ mod ram_helpers { pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256); flash_function_pointers_with_boot2(true, true, &boot2) } else { flash_function_pointers(true, true) @@ -570,7 +595,10 @@ mod ram_helpers { pub unsafe fn flash_range_program(addr: u32, data: &[u8]) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTROM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); flash_function_pointers_with_boot2(false, true, &boot2) } else { flash_function_pointers(false, true) @@ -597,16 +625,8 @@ mod ram_helpers { /// addr must be aligned to 4096 #[inline(never)] #[link_section = ".data.ram_func"] + #[cfg(feature = "rp2040")] unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { - /* - Should be equivalent to: - rom_data::connect_internal_flash(); - rom_data::flash_exit_xip(); - rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected - rom_data::flash_range_program(addr, data as *const _, len); // if selected - rom_data::flash_flush_cache(); - rom_data::flash_enter_cmd_xip(); - */ #[cfg(target_arch = "arm")] core::arch::asm!( "mov r8, r0", @@ -659,6 +679,32 @@ mod ram_helpers { ); } + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// Length of data must be a multiple of 4096 + /// addr must be aligned to 4096 + #[inline(never)] + #[link_section = ".data.ram_func"] + #[cfg(feature = "_rp235x")] + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()); + ((*ptrs).connect_internal_flash)(); + ((*ptrs).flash_exit_xip)(); + if (*ptrs).flash_range_erase.is_some() { + ((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0); + } + if (*ptrs).flash_range_program.is_some() { + ((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize); + } + ((*ptrs).flash_flush_cache)(); + ((*ptrs).flash_enter_cmd_xip)(); + } + #[repr(C)] struct FlashCommand { cmd_addr: *const u8, @@ -692,6 +738,7 @@ mod ram_helpers { /// - DMA must not access flash memory /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] pub unsafe fn flash_unique_id(out: &mut [u8]) { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { @@ -700,6 +747,7 @@ mod ram_helpers { } else { flash_function_pointers(false, false) }; + // 4B - read unique ID let cmd = [0x4B]; read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers); @@ -720,6 +768,7 @@ mod ram_helpers { /// - DMA must not access flash memory /// /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] pub unsafe fn flash_jedec_id() -> u32 { let mut boot2 = [0u32; 256 / 4]; let ptrs = if USE_BOOT2 { @@ -728,6 +777,7 @@ mod ram_helpers { } else { flash_function_pointers(false, false) }; + let mut id = [0u8; 4]; // 9F - read JEDEC ID let cmd = [0x9F]; @@ -735,6 +785,7 @@ mod ram_helpers { u32::from_be_bytes(id) } + #[cfg(feature = "rp2040")] unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) { read_flash_inner( FlashCommand { @@ -758,6 +809,7 @@ mod ram_helpers { /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) #[inline(never)] #[link_section = ".data.ram_func"] + #[cfg(feature = "rp2040")] unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) { #[cfg(target_arch = "arm")] core::arch::asm!( diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index ea87fd9da..520043b07 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -14,7 +14,12 @@ use crate::pac::SIO; use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; const NEW_AW: AtomicWaker = AtomicWaker::new(); -const BANK0_PIN_COUNT: usize = 30; + +#[cfg(any(feature = "rp2040", feature = "rp235xa"))] +pub(crate) const BANK0_PIN_COUNT: usize = 30; +#[cfg(feature = "rp235xb")] +pub(crate) const BANK0_PIN_COUNT: usize = 48; + static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [NEW_AW; BANK0_PIN_COUNT]; #[cfg(feature = "qspi-as-gpio")] const QSPI_PIN_COUNT: usize = 6; @@ -178,6 +183,13 @@ impl<'d> Input<'d> { pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> { self.pin.dormant_wake(cfg) } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } } /// Interrupt trigger levels. @@ -413,6 +425,13 @@ impl<'d> Output<'d> { pub fn toggle(&mut self) { self.pin.toggle() } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } } /// GPIO output open-drain. @@ -539,6 +558,13 @@ impl<'d> OutputOpenDrain<'d> { pub async fn wait_for_any_edge(&mut self) { self.pin.wait_for_any_edge().await; } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } } /// GPIO flexible pin. @@ -560,11 +586,16 @@ impl<'d> Flex<'d> { into_ref!(pin); pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); w.set_ie(true); }); pin.gpio().ctrl().write(|w| { + #[cfg(feature = "rp2040")] w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0 as _); + #[cfg(feature = "_rp235x")] + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIOB_PROC_0 as _); }); Self { pin: pin.map_into() } @@ -760,6 +791,15 @@ impl<'d> Flex<'d> { cfg, } } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_iso(isolate); + }); + } } impl<'d> Drop for Flex<'d> { @@ -806,12 +846,12 @@ pub(crate) trait SealedPin: Sized { #[inline] fn _pin(&self) -> u8 { - self.pin_bank() & 0x1f + self.pin_bank() & 0x7f } #[inline] fn _bank(&self) -> Bank { - match self.pin_bank() >> 5 { + match self.pin_bank() >> 7 { #[cfg(feature = "qspi-as-gpio")] 1 => Bank::Qspi, _ => Bank::Bank0, @@ -840,15 +880,27 @@ pub(crate) trait SealedPin: Sized { } fn sio_out(&self) -> pac::sio::Gpio { - SIO.gpio_out(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_out(self._bank() as _) + } else { + SIO.gpio_out((self._pin() / 32) as _) + } } fn sio_oe(&self) -> pac::sio::Gpio { - SIO.gpio_oe(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_oe(self._bank() as _) + } else { + SIO.gpio_oe((self._pin() / 32) as _) + } } fn sio_in(&self) -> Reg { - SIO.gpio_in(self._bank() as _) + if cfg!(feature = "rp2040") { + SIO.gpio_in(self._bank() as _) + } else { + SIO.gpio_in((self._pin() / 32) as _) + } } fn int_proc(&self) -> pac::io::Int { @@ -913,7 +965,7 @@ macro_rules! impl_pin { impl SealedPin for peripherals::$name { #[inline] fn pin_bank(&self) -> u8 { - ($bank as u8) * 32 + $pin_num + ($bank as u8) * 128 + $pin_num } } @@ -956,6 +1008,44 @@ impl_pin!(PIN_27, Bank::Bank0, 27); impl_pin!(PIN_28, Bank::Bank0, 28); impl_pin!(PIN_29, Bank::Bank0, 29); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, Bank::Bank0, 30); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, Bank::Bank0, 31); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, Bank::Bank0, 32); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, Bank::Bank0, 33); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, Bank::Bank0, 34); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, Bank::Bank0, 35); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, Bank::Bank0, 36); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, Bank::Bank0, 37); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, Bank::Bank0, 38); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, Bank::Bank0, 39); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, Bank::Bank0, 40); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, Bank::Bank0, 41); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, Bank::Bank0, 42); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, Bank::Bank0, 43); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, Bank::Bank0, 44); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, Bank::Bank0, 45); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, Bank::Bank0, 46); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, Bank::Bank0, 47); + +// TODO rp235x bank1 as gpio support #[cfg(feature = "qspi-as-gpio")] impl_pin!(PIN_QSPI_SCLK, Bank::Qspi, 0); #[cfg(feature = "qspi-as-gpio")] diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs index 10d3c86b3..32778215f 100644 --- a/embassy-rp/src/i2c.rs +++ b/embassy-rp/src/i2c.rs @@ -312,13 +312,13 @@ impl<'d, T: Instance> I2c<'d, T, Async> { } } - /// Read from address into buffer using DMA. + /// Read from address into buffer asynchronously. pub async fn read_async(&mut self, addr: impl Into, buffer: &mut [u8]) -> Result<(), Error> { Self::setup(addr.into())?; self.read_async_internal(buffer, true, true).await } - /// Write to address from buffer using DMA. + /// Write to address from buffer asynchronously. pub async fn write_async( &mut self, addr: impl Into, @@ -328,7 +328,7 @@ impl<'d, T: Instance> I2c<'d, T, Async> { self.write_async_internal(bytes, true).await } - /// Write to address from bytes and read from address into buffer using DMA. + /// Write to address from bytes and read from address into buffer asynchronously. pub async fn write_read_async( &mut self, addr: impl Into, @@ -363,6 +363,8 @@ where { pin.gpio().ctrl().write(|w| w.set_funcsel(3)); pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); w.set_schmitt(true); w.set_slewfast(false); w.set_ie(true); @@ -779,9 +781,6 @@ pub fn i2c_reserved_addr(addr: u16) -> bool { } pub(crate) trait SealedInstance { - const TX_DREQ: u8; - const RX_DREQ: u8; - fn regs() -> crate::pac::i2c::I2c; fn reset() -> crate::pac::resets::regs::Peripherals; fn waker() -> &'static AtomicWaker; @@ -816,11 +815,8 @@ pub trait Instance: SealedInstance { } macro_rules! impl_instance { - ($type:ident, $irq:ident, $reset:ident, $tx_dreq:expr, $rx_dreq:expr) => { + ($type:ident, $irq:ident, $reset:ident) => { impl SealedInstance for peripherals::$type { - const TX_DREQ: u8 = $tx_dreq; - const RX_DREQ: u8 = $rx_dreq; - #[inline] fn regs() -> pac::i2c::I2c { pac::$type @@ -846,8 +842,8 @@ macro_rules! impl_instance { }; } -impl_instance!(I2C0, I2C0_IRQ, set_i2c0, 32, 33); -impl_instance!(I2C1, I2C1_IRQ, set_i2c1, 34, 35); +impl_instance!(I2C0, I2C0_IRQ, set_i2c0); +impl_instance!(I2C1, I2C1_IRQ, set_i2c1); /// SDA pin. pub trait SdaPin: crate::gpio::Pin {} @@ -890,3 +886,39 @@ impl_pin!(PIN_26, I2C1, SdaPin); impl_pin!(PIN_27, I2C1, SclPin); impl_pin!(PIN_28, I2C0, SdaPin); impl_pin!(PIN_29, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, I2C1, SclPin); diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 471e7f8b1..d402cf793 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -9,28 +9,41 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +#[cfg(feature = "binary-info")] +pub use rp_binary_info as binary_info; + #[cfg(feature = "critical-section-impl")] mod critical_section_impl; +#[cfg(feature = "rp2040")] mod intrinsics; pub mod adc; +#[cfg(feature = "_rp235x")] +pub mod block; +#[cfg(feature = "rp2040")] pub mod bootsel; pub mod clocks; pub mod dma; pub mod flash; +#[cfg(feature = "rp2040")] mod float; pub mod gpio; pub mod i2c; pub mod i2c_slave; pub mod multicore; +#[cfg(feature = "_rp235x")] +pub mod otp; pub mod pwm; mod reset; pub mod rom_data; +#[cfg(feature = "rp2040")] pub mod rtc; pub mod spi; #[cfg(feature = "time-driver")] pub mod time_driver; +#[cfg(feature = "_rp235x")] +pub mod trng; pub mod uart; pub mod usb; pub mod watchdog; @@ -49,6 +62,7 @@ pub(crate) use rp_pac as pac; #[cfg(feature = "rt")] pub use crate::pac::NVIC_PRIO_BITS; +#[cfg(feature = "rp2040")] embassy_hal_internal::interrupt_mod!( TIMER_IRQ_0, TIMER_IRQ_1, @@ -84,6 +98,54 @@ embassy_hal_internal::interrupt_mod!( SWI_IRQ_5, ); +#[cfg(feature = "_rp235x")] +embassy_hal_internal::interrupt_mod!( + TIMER0_IRQ_0, + TIMER0_IRQ_1, + TIMER0_IRQ_2, + TIMER0_IRQ_3, + TIMER1_IRQ_0, + TIMER1_IRQ_1, + TIMER1_IRQ_2, + TIMER1_IRQ_3, + PWM_IRQ_WRAP_0, + PWM_IRQ_WRAP_1, + DMA_IRQ_0, + DMA_IRQ_1, + USBCTRL_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + PIO2_IRQ_0, + PIO2_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_BANK0_NS, + IO_IRQ_QSPI, + IO_IRQ_QSPI_NS, + SIO_IRQ_FIFO, + SIO_IRQ_BELL, + SIO_IRQ_FIFO_NS, + SIO_IRQ_BELL_NS, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + TRNG_IRQ, + PLL_SYS_IRQ, + PLL_USB_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + /// Macro to bind interrupts to handlers. /// /// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) @@ -123,6 +185,7 @@ macro_rules! bind_interrupts { }; } +#[cfg(feature = "rp2040")] embassy_hal_internal::peripherals! { PIN_0, PIN_1, @@ -210,7 +273,142 @@ embassy_hal_internal::peripherals! { BOOTSEL, } -#[cfg(not(feature = "boot2-none"))] +#[cfg(feature = "_rp235x")] +embassy_hal_internal::peripherals! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, + #[cfg(feature = "rp235xb")] + PIN_30, + #[cfg(feature = "rp235xb")] + PIN_31, + #[cfg(feature = "rp235xb")] + PIN_32, + #[cfg(feature = "rp235xb")] + PIN_33, + #[cfg(feature = "rp235xb")] + PIN_34, + #[cfg(feature = "rp235xb")] + PIN_35, + #[cfg(feature = "rp235xb")] + PIN_36, + #[cfg(feature = "rp235xb")] + PIN_37, + #[cfg(feature = "rp235xb")] + PIN_38, + #[cfg(feature = "rp235xb")] + PIN_39, + #[cfg(feature = "rp235xb")] + PIN_40, + #[cfg(feature = "rp235xb")] + PIN_41, + #[cfg(feature = "rp235xb")] + PIN_42, + #[cfg(feature = "rp235xb")] + PIN_43, + #[cfg(feature = "rp235xb")] + PIN_44, + #[cfg(feature = "rp235xb")] + PIN_45, + #[cfg(feature = "rp235xb")] + PIN_46, + #[cfg(feature = "rp235xb")] + PIN_47, + PIN_QSPI_SCLK, + PIN_QSPI_SS, + PIN_QSPI_SD0, + PIN_QSPI_SD1, + PIN_QSPI_SD2, + PIN_QSPI_SD3, + + UART0, + UART1, + + SPI0, + SPI1, + + I2C0, + I2C1, + + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + DMA_CH12, + DMA_CH13, + DMA_CH14, + DMA_CH15, + + PWM_SLICE0, + PWM_SLICE1, + PWM_SLICE2, + PWM_SLICE3, + PWM_SLICE4, + PWM_SLICE5, + PWM_SLICE6, + PWM_SLICE7, + PWM_SLICE8, + PWM_SLICE9, + PWM_SLICE10, + PWM_SLICE11, + + USB, + + RTC, + + FLASH, + + ADC, + ADC_TEMP_SENSOR, + + CORE1, + + PIO0, + PIO1, + PIO2, + + WATCHDOG, + BOOTSEL, + + TRNG +} + +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] macro_rules! select_bootloader { ( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => { $( @@ -227,7 +425,7 @@ macro_rules! select_bootloader { } } -#[cfg(not(feature = "boot2-none"))] +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] select_bootloader! { "boot2-at25sf128a" => BOOT_LOADER_AT25SF128A, "boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS, @@ -279,6 +477,7 @@ pub fn install_core0_stack_guard() -> Result<(), ()> { unsafe { install_stack_guard(core::ptr::addr_of_mut!(_stack_end)) } } +#[cfg(all(feature = "rp2040", not(feature = "_test")))] #[inline(always)] fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { let core = unsafe { cortex_m::Peripherals::steal() }; @@ -306,6 +505,32 @@ fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { Ok(()) } +#[cfg(all(feature = "_rp235x", not(feature = "_test")))] +#[inline(always)] +fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Fail if MPU is already configured + if core.MPU.ctrl.read() != 0 { + return Err(()); + } + + unsafe { + core.MPU.ctrl.write(5); // enable mpu with background default map + core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address + core.MPU.rlar.write(1); // enable region + } + Ok(()) +} + +// This is to hack around cortex_m defaulting to ARMv7 when building tests, +// so the compile fails when we try to use ARMv8 peripherals. +#[cfg(feature = "_test")] +#[inline(always)] +fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { + Ok(()) +} + /// HAL configuration for RP. pub mod config { use crate::clocks::ClockConfig; @@ -354,7 +579,7 @@ pub fn init(config: config::Config) -> Peripherals { peripherals } -#[cfg(feature = "rt")] +#[cfg(all(feature = "rt", feature = "rp2040"))] #[cortex_m_rt::pre_init] unsafe fn pre_init() { // SIO does not get reset when core0 is reset with either `scb::sys_reset()` or with SWD. diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs index d9d65694a..9f7d77bf5 100644 --- a/embassy-rp/src/multicore.rs +++ b/embassy-rp/src/multicore.rs @@ -84,7 +84,7 @@ impl Stack { } } -#[cfg(feature = "rt")] +#[cfg(all(feature = "rt", feature = "rp2040"))] #[interrupt] #[link_section = ".data.ram_func"] unsafe fn SIO_IRQ_PROC1() { @@ -109,6 +109,31 @@ unsafe fn SIO_IRQ_PROC1() { } } +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +#[link_section = ".data.ram_func"] +unsafe fn SIO_IRQ_FIFO() { + let sio = pac::SIO; + // Clear IRQ + sio.fifo().st().write(|w| w.set_wof(false)); + + while sio.fifo().st().read().vld() { + // Pause CORE1 execution and disable interrupts + if fifo_read_wfe() == PAUSE_TOKEN { + cortex_m::interrupt::disable(); + // Signal to CORE0 that execution is paused + fifo_write(PAUSE_TOKEN); + // Wait for `resume` signal from CORE0 + while fifo_read_wfe() != RESUME_TOKEN { + cortex_m::asm::nop(); + } + cortex_m::interrupt::enable(); + // Signal to CORE0 that execution is resumed + fifo_write(RESUME_TOKEN); + } + } +} + /// Spawn a function on this core pub fn spawn_core1(_core1: CORE1, stack: &'static mut Stack, entry: F) where @@ -135,7 +160,14 @@ where IS_CORE1_INIT.store(true, Ordering::Release); // Enable fifo interrupt on CORE1 for `pause` functionality. - unsafe { interrupt::SIO_IRQ_PROC1.enable() }; + #[cfg(feature = "rp2040")] + unsafe { + interrupt::SIO_IRQ_PROC1.enable() + }; + #[cfg(feature = "_rp235x")] + unsafe { + interrupt::SIO_IRQ_FIFO.enable() + }; entry() } diff --git a/embassy-rp/src/otp.rs b/embassy-rp/src/otp.rs new file mode 100644 index 000000000..cdaf5bded --- /dev/null +++ b/embassy-rp/src/otp.rs @@ -0,0 +1,108 @@ +//! Interface to the RP2350's One Time Programmable Memory + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +/// The ways in which we can fail to read OTP +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The user passed an invalid index to a function. + InvalidIndex, + /// The hardware refused to let us read this word, probably due to + /// read lock set earlier in the boot process. + InvalidPermissions, +} + +/// OTP read address, using automatic Error Correction. +/// +/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or +/// all-ones on permission failure. Only the first 8 KiB is populated. +pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32; + +/// OTP read address, without using any automatic Error Correction. +/// +/// A 32-bit read returns 24-bits of raw data from the OTP word. +pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32; + +/// How many pages in OTP (post error-correction) +pub const NUM_PAGES: usize = 64; + +/// How many rows in one page in OTP (post error-correction) +pub const NUM_ROWS_PER_PAGE: usize = 64; + +/// How many rows in OTP (post error-correction) +pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE; + +/// Read one ECC protected word from the OTP +pub fn read_ecc_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // First do a raw read to check permissions + let _ = read_raw_word(row)?; + // One 32-bit read gets us two rows + let offset = row >> 1; + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_BASE.add(offset).read() }; + if (row & 1) == 0 { + Ok(value as u16) + } else { + Ok((value >> 16) as u16) + } +} + +/// Read one raw word from the OTP +/// +/// You get the 24-bit raw value in the lower part of the 32-bit result. +pub fn read_raw_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // One 32-bit read gets us one row + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() }; + if value == 0xFFFF_FFFF { + Err(Error::InvalidPermissions) + } else { + Ok(value) + } +} + +/// Get the random 64bit chipid from rows 0x0-0x3. +pub fn get_chipid() -> Result { + let w0 = read_ecc_word(0x000)?.to_be_bytes(); + let w1 = read_ecc_word(0x001)?.to_be_bytes(); + let w2 = read_ecc_word(0x002)?.to_be_bytes(); + let w3 = read_ecc_word(0x003)?.to_be_bytes(); + + Ok(u64::from_be_bytes([ + w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} + +/// Get the 128bit private random number from rows 0x4-0xb. +/// +/// This ID is not exposed through the USB PICOBOOT GET_INFO command +/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP +/// access point can read the entirety of page 0, so this value is not +/// meaningfully private unless the USB PICOBOOT interface is disabled via the +//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0 +pub fn get_private_random_number() -> Result { + let w0 = read_ecc_word(0x004)?.to_be_bytes(); + let w1 = read_ecc_word(0x005)?.to_be_bytes(); + let w2 = read_ecc_word(0x006)?.to_be_bytes(); + let w3 = read_ecc_word(0x007)?.to_be_bytes(); + let w4 = read_ecc_word(0x008)?.to_be_bytes(); + let w5 = read_ecc_word(0x009)?.to_be_bytes(); + let w6 = read_ecc_word(0x00a)?.to_be_bytes(); + let w7 = read_ecc_word(0x00b)?.to_be_bytes(); + + Ok(u128::from_be_bytes([ + w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} diff --git a/embassy-rp/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs index 1f7adbda3..72aa8f104 100644 --- a/embassy-rp/src/pio/mod.rs +++ b/embassy-rp/src/pio/mod.rs @@ -5,19 +5,16 @@ use core::pin::Pin as FuturePin; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::{Context, Poll}; -use atomic_polyfill::{AtomicU32, AtomicU8}; +use atomic_polyfill::{AtomicU64, AtomicU8}; use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::extra::U8; use fixed::FixedU32; -use pac::io::vals::Gpio0ctrlFuncsel; -use pac::pio::vals::SmExecctrlStatusSel; use pio::{Program, SideSet, Wrap}; use crate::dma::{Channel, Transfer, Word}; use crate::gpio::{self, AnyPin, Drive, Level, Pull, SealedPin, SlewRate}; use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; -use crate::pac::dma::vals::TreqSel; use crate::relocate::RelocatedProgram; use crate::{pac, peripherals, RegExt}; @@ -355,11 +352,14 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { let p = ch.regs(); p.write_addr().write_value(data.as_ptr() as u32); p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); - p.trans_count().write_value(data.len() as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = data.len() as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(data.len() as u32)); compiler_fence(Ordering::SeqCst); p.ctrl_trig().write(|w| { // Set RX DREQ for this statemachine - w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8 + 4)); + w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8 + 4)); w.set_data_size(W::size()); w.set_chain_to(ch.number()); w.set_incr_read(false); @@ -437,11 +437,14 @@ impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { let p = ch.regs(); p.read_addr().write_value(data.as_ptr() as u32); p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32); - p.trans_count().write_value(data.len() as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = data.len() as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(data.len() as u32)); compiler_fence(Ordering::SeqCst); p.ctrl_trig().write(|w| { // Set TX DREQ for this statemachine - w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8)); + w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8)); w.set_data_size(W::size()); w.set_chain_to(ch.number()); w.set_incr_read(true); @@ -523,6 +526,39 @@ pub struct PinConfig { pub out_base: u8, } +/// Comparison level or IRQ index for the MOV x, STATUS instruction. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(feature = "_rp235x")] +pub enum StatusN { + /// IRQ flag in this PIO block + This(u8), + /// IRQ flag in the next lower PIO block + Lower(u8), + /// IRQ flag in the next higher PIO block + Higher(u8), +} + +#[cfg(feature = "_rp235x")] +impl Default for StatusN { + fn default() -> Self { + Self::This(0) + } +} + +#[cfg(feature = "_rp235x")] +impl Into for StatusN { + fn into(self) -> crate::pac::pio::vals::ExecctrlStatusN { + let x = match self { + StatusN::This(n) => n, + StatusN::Lower(n) => n + 0x08, + StatusN::Higher(n) => n + 0x10, + }; + + crate::pac::pio::vals::ExecctrlStatusN(x) + } +} + /// PIO config. #[derive(Clone, Copy, Debug)] pub struct Config<'d, PIO: Instance> { @@ -537,7 +573,12 @@ pub struct Config<'d, PIO: Instance> { /// Which source to use for checking status. pub status_sel: StatusSource, /// Status comparison level. + #[cfg(feature = "rp2040")] pub status_n: u8, + // This cfg probably shouldn't be required, but the SVD for the 2040 doesn't have the enum + #[cfg(feature = "_rp235x")] + /// Status comparison level. + pub status_n: StatusN, exec: ExecConfig, origin: Option, /// Configure FIFO allocation. @@ -653,7 +694,7 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { assert!(config.clock_divider <= 65536, "clkdiv must be <= 65536"); assert!(config.clock_divider >= 1, "clkdiv must be >= 1"); assert!(config.out_en_sel < 32, "out_en_sel must be < 32"); - assert!(config.status_n < 32, "status_n must be < 32"); + //assert!(config.status_n < 32, "status_n must be < 32"); // sm expects 0 for 32, truncation makes that happen assert!(config.shift_in.threshold <= 32, "shift_in.threshold must be <= 32"); assert!(config.shift_out.threshold <= 32, "shift_out.threshold must be <= 32"); @@ -668,11 +709,17 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_out_sticky(config.out_sticky); w.set_wrap_top(config.exec.wrap_top); w.set_wrap_bottom(config.exec.wrap_bottom); + #[cfg(feature = "_rp235x")] w.set_status_sel(match config.status_sel { - StatusSource::TxFifoLevel => SmExecctrlStatusSel::TXLEVEL, - StatusSource::RxFifoLevel => SmExecctrlStatusSel::RXLEVEL, + StatusSource::TxFifoLevel => pac::pio::vals::ExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => pac::pio::vals::ExecctrlStatusSel::RXLEVEL, }); - w.set_status_n(config.status_n); + #[cfg(feature = "rp2040")] + w.set_status_sel(match config.status_sel { + StatusSource::TxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::RXLEVEL, + }); + w.set_status_n(config.status_n.into()); }); sm.shiftctrl().write(|w| { w.set_fjoin_rx(config.fifo_join == FifoJoin::RxOnly); @@ -684,6 +731,8 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_autopull(config.shift_out.auto_fill); w.set_autopush(config.shift_in.auto_fill); }); + + #[cfg(feature = "rp2040")] sm.pinctrl().write(|w| { w.set_sideset_count(config.pins.sideset_count); w.set_set_count(config.pins.set_count); @@ -693,6 +742,52 @@ impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { w.set_set_base(config.pins.set_base); w.set_out_base(config.pins.out_base); }); + + #[cfg(feature = "_rp235x")] + { + let mut low_ok = true; + let mut high_ok = true; + + let in_pins = config.pins.in_base..config.pins.in_base + config.in_count; + let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count; + let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count; + let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count; + + for pin_range in [in_pins, side_pins, set_pins, out_pins] { + for pin in pin_range { + low_ok &= pin < 32; + high_ok &= pin >= 16; + } + } + + if !low_ok && !high_ok { + panic!( + "All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}", + config.pins.in_base, + config.pins.in_base + config.in_count - 1, + config.pins.sideset_base, + config.pins.sideset_base + config.pins.sideset_count - 1, + config.pins.set_base, + config.pins.set_base + config.pins.set_count - 1, + config.pins.out_base, + config.pins.out_base + config.pins.out_count - 1, + ) + } + let shift = if low_ok { 0 } else { 16 }; + + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default()); + w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default()); + w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default()); + w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default()); + }); + + PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16)); + } + if let Some(origin) = config.origin { unsafe { instr::exec_jmp(self, origin) } } @@ -959,6 +1054,10 @@ impl<'d, PIO: Instance> Common<'d, PIO> { pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { into_ref!(pin); pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + #[cfg(feature = "_rp235x")] + pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }); // we can be relaxed about this because we're &mut here and nothing is cached PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); Pin { @@ -1140,16 +1239,15 @@ impl<'d, PIO: Instance> Pio<'d, PIO> { // other way. pub struct State { users: AtomicU8, - used_pins: AtomicU32, + used_pins: AtomicU64, } fn on_pio_drop() { let state = PIO::state(); if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { let used_pins = state.used_pins.load(Ordering::Relaxed); - let null = Gpio0ctrlFuncsel::NULL as _; - // we only have 30 pins. don't test the other two since gpio() asserts. - for i in 0..30 { + let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; + for i in 0..crate::gpio::BANK0_PIN_COUNT { if used_pins & (1 << i) != 0 { pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); } @@ -1174,7 +1272,7 @@ trait SealedInstance { fn state() -> &'static State { static STATE: State = State { users: AtomicU8::new(0), - used_pins: AtomicU32::new(0), + used_pins: AtomicU64::new(0), }; &STATE @@ -1203,6 +1301,8 @@ macro_rules! impl_pio { impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0); impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0); +#[cfg(feature = "_rp235x")] +impl_pio!(PIO2, 2, PIO2, PIO2_0, PIO2_IRQ_0); /// PIO pin. pub trait PioPin: gpio::Pin {} @@ -1247,3 +1347,25 @@ impl_pio_pin! { PIN_28, PIN_29, } + +#[cfg(feature = "rp235xb")] +impl_pio_pin! { + PIN_30, + PIN_31, + PIN_32, + PIN_33, + PIN_34, + PIN_35, + PIN_36, + PIN_37, + PIN_38, + PIN_39, + PIN_40, + PIN_41, + PIN_42, + PIN_43, + PIN_44, + PIN_45, + PIN_46, + PIN_47, +} diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs index c35e76587..7da3dccb0 100644 --- a/embassy-rp/src/pwm.rs +++ b/embassy-rp/src/pwm.rs @@ -110,6 +110,8 @@ impl<'d> Pwm<'d> { if let Some(pin) = &b { pin.gpio().ctrl().write(|w| w.set_funcsel(4)); pin.pad_ctrl().modify(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); w.set_pue(b_pull == Pull::Up); w.set_pde(b_pull == Pull::Down); }); @@ -363,6 +365,15 @@ slice!(PWM_SLICE5, 5); slice!(PWM_SLICE6, 6); slice!(PWM_SLICE7, 7); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE8, 8); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE9, 9); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE10, 10); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE11, 11); + /// PWM Channel A. pub trait ChannelAPin: GpioPin {} /// PWM Channel B. @@ -404,3 +415,39 @@ impl_pin!(PIN_26, PWM_SLICE5, ChannelAPin); impl_pin!(PIN_27, PWM_SLICE5, ChannelBPin); impl_pin!(PIN_28, PWM_SLICE6, ChannelAPin); impl_pin!(PIN_29, PWM_SLICE6, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, PWM_SLICE7, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, PWM_SLICE7, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, PWM_SLICE8, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, PWM_SLICE8, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, PWM_SLICE9, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, PWM_SLICE9, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, PWM_SLICE10, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, PWM_SLICE10, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, PWM_SLICE11, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, PWM_SLICE11, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, PWM_SLICE8, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, PWM_SLICE8, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, PWM_SLICE9, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, PWM_SLICE9, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, PWM_SLICE10, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, PWM_SLICE10, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, PWM_SLICE11, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, PWM_SLICE11, ChannelBPin); diff --git a/embassy-rp/src/reset.rs b/embassy-rp/src/reset.rs index 70512fa14..4b9e42483 100644 --- a/embassy-rp/src/reset.rs +++ b/embassy-rp/src/reset.rs @@ -2,7 +2,7 @@ pub use pac::resets::regs::Peripherals; use crate::pac; -pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ffffff); +pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ff_ffff); pub(crate) fn reset(peris: Peripherals) { pac::RESETS.reset().write_value(peris); diff --git a/embassy-rp/src/rom_data/mod.rs b/embassy-rp/src/rom_data/mod.rs new file mode 100644 index 000000000..e5fcf8e3c --- /dev/null +++ b/embassy-rp/src/rom_data/mod.rs @@ -0,0 +1,33 @@ +#![cfg_attr( + feature = "rp2040", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. +" +)] +#![cfg_attr( + feature = "_rp235x", + doc = r" +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device +" +)] + +#[cfg_attr(feature = "rp2040", path = "rp2040.rs")] +#[cfg_attr(feature = "_rp235x", path = "rp235x.rs")] +mod inner; +pub use inner::*; diff --git a/embassy-rp/src/rom_data.rs b/embassy-rp/src/rom_data/rp2040.rs similarity index 99% rename from embassy-rp/src/rom_data.rs rename to embassy-rp/src/rom_data/rp2040.rs index baebe5b6c..5a74eddd6 100644 --- a/embassy-rp/src/rom_data.rs +++ b/embassy-rp/src/rom_data/rp2040.rs @@ -189,7 +189,7 @@ macro_rules! rom_functions { declare_rom_function! { $(#[$outer])* fn $name( $($argname: $ty),* ) -> $ret { - $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) } } @@ -205,7 +205,7 @@ macro_rules! rom_functions { declare_rom_function! { $(#[$outer])* unsafe fn $name( $($argname: $ty),* ) -> $ret { - $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) } } diff --git a/embassy-rp/src/rom_data/rp235x.rs b/embassy-rp/src/rom_data/rp235x.rs new file mode 100644 index 000000000..b16fee8f7 --- /dev/null +++ b/embassy-rp/src/rom_data/rp235x.rs @@ -0,0 +1,752 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for the tag which matches the mask. +type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +#[allow(unused)] +mod rt_flags { + pub const FUNC_RISCV: u32 = 0x0001; + pub const FUNC_RISCV_FAR: u32 = 0x0003; + pub const FUNC_ARM_SEC: u32 = 0x0004; + // reserved for 32-bit pointer: 0x0008 + pub const FUNC_ARM_NONSEC: u32 = 0x0010; + // reserved for 32-bit pointer: 0x0020 + pub const DATA: u32 = 0x0040; + // reserved for 32-bit pointer: 0x0080 + #[cfg(all(target_arch = "arm", target_os = "none"))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC; + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV; +} + +/// Retrieve rom content from a table using a code. +pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_TABLE_LOOKUP_A1.read() as usize + } else { + ROM_TABLE_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// Retrieve rom data content from a table using a code. +pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_DATA_LOOKUP_A1.read() as usize + } else { + ROM_DATA_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the rp235x datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +// **************** 5.5.7 Low-level Flash Commands **************** + +declare_rom_function! { + /// Restore all QSPI pad controls to their default state, and connect the + /// QMI peripheral to the QSPI pads. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn connect_internal_flash() -> () { + crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Initialise the QMI for serial operations (direct mode) + /// + /// Also initialise a basic XIP mode, where the QMI will perform 03h serial + /// read commands at low speed (CLKDIV=12) in response to XIP reads. + /// + /// Then, issue a sequence to the QSPI device on chip select 0, designed to + /// return it from continuous read mode ("XIP mode") and/or QPI mode to a + /// state where it will accept serial commands. This is necessary after + /// system reset to restore the QSPI device to a known state, because + /// resetting RP2350 does not reset attached QSPI devices. It is also + /// necessary when user code, having already performed some + /// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI + /// device to a state where it will accept the serial erase and programming + /// commands issued by the bootrom’s flash access functions. + /// + /// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO, + /// then the XIP exit sequence is also issued to chip select 1. + /// + /// The QSPI device should be accessible for XIP reads after calling this + /// function; the name flash_exit_xip refers to returning the QSPI device + /// from its XIP state to a serial command state. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_exit_xip() -> () { + crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Erase count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the + /// size of the block erased by this command — this function will use the + /// larger block erase where possible, for much higher erase speed. addr + /// must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API, which can be achieved by calling connect_internal_flash() followed + /// by flash_exit_xip(). After the erase, the flash cache should be flushed + /// via flash_flush_cache() to ensure the modified flash data is visible to + /// cached XIP accesses. + /// + /// Finally, the original XIP mode should be restored by copying the saved + /// XIP setup function from bootram into SRAM, and executing it: the bootrom + /// provides a default function which restores the flash mode/clkdiv + /// discovered during flash scanning, and user programs can override this + /// with their own XIP setup function. + /// + /// For the duration of the erase operation, QMI is in direct mode (Section + /// 12.14.5) and attempting to access XIP from DMA, the debugger or the + /// other core will return a bus fault. XIP becomes accessible again once + /// the function returns. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () { + crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Program data to a range of flash storage addresses starting at addr + /// (offset from the start of flash) and count bytes in size. + /// + /// `addr` must be aligned to a 256-byte boundary, and count must be a + /// multiple of 256. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API — see notes on flash_range_erase(). + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () { + crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Flush the entire XIP cache, by issuing an invalidate by set/way + /// maintenance operation to every cache line (Section 4.4.1). + /// + /// This ensures that flash program/erase operations are visible to + /// subsequent cached XIP reads. + /// + /// Note that this unpins pinned cache lines, which may interfere with + /// cache-as-SRAM use of the XIP cache. + /// + /// No other operations are performed. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_flush_cache() -> () { + crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure the QMI to generate a standard 03h serial read command, with + /// 24 address bits, upon each XIP access. + /// + /// This is a slow XIP configuration, but is widely supported. CLKDIV is set + /// to 12. The debugger may call this function to ensure that flash is + /// readable following a program/erase operation. + /// + /// Note that the same setup is performed by flash_exit_xip(), and the + /// RP2350 flash program/erase functions do not leave XIP in an inaccessible + /// state, so calls to this function are largely redundant. It is provided + /// for compatibility with RP2040. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_enter_cmd_xip() -> () { + crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure QMI for one of a small menu of XIP read modes supported by the + /// bootrom. This mode is configured for both memory windows (both chip + /// selects), and the clock divisor is also applied to direct mode. + /// + /// The available modes are: + /// + /// * 0: `03h` serial read: serial address, serial data, no wait cycles + /// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles + /// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles + /// (including MODE bits, which are driven to 0) + /// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles + /// (including MODE bits, which are driven to 0) + /// + /// The XIP write command/format are not configured by this function. When + /// booting from flash, the bootrom tries each of these modes in turn, from + /// 3 down to 0. The first mode that is found to work is remembered, and a + /// default XIP setup function is written into bootram that calls this + /// function (flash_select_xip_read_mode) with the parameters discovered + /// during flash scanning. This can be called at any time to restore the + /// flash parameters discovered during flash boot. + /// + /// All XIP modes configured by the bootrom have an 8-bit serial command + /// prefix, so that the flash can remain in a serial command state, meaning + /// XIP accesses can be mixed more freely with program/erase serial + /// operations. This has a performance penalty, so users can perform their + /// own flash setup after flash boot using continuous read mode or QPI mode + /// to avoid or alleviate the command prefix cost. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () { + crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Restore the QMI address translation registers, ATRANS0 through ATRANS7, + /// to their reset state. This makes the runtime- to-storage address map an + /// identity map, i.e. the mapped and unmapped address are equal, and the + /// entire space is fully mapped. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_reset_address_trans() -> () { + crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** High-level Flash Commands **************** + +declare_rom_function! { + /// Applies the address translation currently configured by QMI address + /// translation registers, ATRANS0 through ATRANS7. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Translating an address outside of the XIP runtime address window, or + /// beyond the bounds of an ATRANSx_SIZE field, returns + /// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage + /// address. Otherwise, return the storage address which QMI would access + /// when presented with the runtime address addr. This is effectively a + /// virtual-to-physical address translation for QMI. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_runtime_to_storage_addr()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Perform a flash read, erase, or program operation. + /// + /// Erase operations must be sector-aligned (4096 bytes) and sector- + /// multiple-sized, and program operations must be page-aligned (256 bytes) + /// and page-multiple-sized; misaligned erase and program operations will + /// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program + /// — is selected by the CFLASH_OP_BITS bitfield of the flags argument. + /// + /// See datasheet section 5.5.8.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_op()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Security Related Functions **************** + +declare_rom_function! { + /// Allow or disallow the specific NS API (note all NS APIs default to + /// disabled). + /// + /// See datasheet section 5.5.9.1 for more details. + /// + /// Supported architectures: ARM-S + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC) + } +} + +declare_rom_function! { + /// Utility method that can be used by secure ARM code to validate a buffer + /// passed to it from Non-secure code. + /// + /// See datasheet section 5.5.9.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn validate_ns_buffer() -> () { + crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Miscellaneous Functions **************** + +declare_rom_function! { + /// Resets the RP2350 and uses the watchdog facility to restart. + /// + /// See datasheet section 5.5.10.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [reboot()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Resets internal bootrom state. + /// + /// See datasheet section 5.5.10.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn bootrom_state_reset(flags: u32) -> () { + crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Set a boot ROM callback. + /// + /// The only supported callback_number is 0 which sets the callback used for + /// the secure_call API. + /// + /// See datasheet section 5.5.10.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 { + crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** System Information Functions **************** + +declare_rom_function! { + /// Fills a buffer with various system information. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_sys_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Fills a buffer with information from the partition table. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_partition_table_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Loads the current partition table from flash, if present. + /// + /// See datasheet section 5.5.11.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 { + crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Writes data from a buffer into OTP, or reads data from OTP into a buffer. + /// + /// See datasheet section 5.5.11.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [otp_access()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Boot Related Functions **************** + +declare_rom_function! { + /// Determines which of the partitions has the "better" IMAGE_DEF. In the + /// case of executable images, this is the one that would be booted. + /// + /// See datasheet section 5.5.12.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Searches a memory region for a launchable image, and executes it if + /// possible. + /// + /// See datasheet section 5.5.12.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Perform an "explicit" buy of an executable launched via an IMAGE_DEF + /// which was "explicit buy" flagged. + /// + /// See datasheet section 5.5.12.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Not yet documented. + /// + /// See datasheet section 5.5.12.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Returns: The index of the B partition of partition A if a partition + /// table is present and loaded, and there is a partition A with a B + /// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND. + /// + /// See datasheet section 5.5.12.5 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_b_partition(partition_a: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Non-secure-specific Functions **************** + +// NB: The "secure_call" function should be here, but it doesn't have a fixed +// function signature as it is designed to let you bounce into any secure +// function from non-secure mode. + +// **************** RISC-V Functions **************** + +declare_rom_function! { + /// Set stack for RISC-V bootrom functions to use. + /// + /// See datasheet section 5.5.14.1 for more details. + /// + /// Supported architectures: RISC-V + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::inner::rt_flags::FUNC_RISCV) + } +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32; + unsafe { ptr.read() } +} + +/// A pointer to the resident partition table info. +/// +/// The resident partition table is the subset of the full partition table that +/// is kept in memory, and used for flash permissions. +pub fn partition_table_pointer() -> *const u32 { + let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32; + unsafe { ptr.read() } +} + +/// Determine if we are in secure mode +/// +/// Returns `true` if we are in secure mode and `false` if we are in non-secure +/// mode. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub fn is_secure_mode() -> bool { + // Look at the start of ROM, which is always readable + #[allow(clippy::zero_ptr)] + let rom_base: *mut u32 = 0x0000_0000 as *mut u32; + // Use the 'tt' instruction to check the permissions for that address + let tt = cortex_m::asm::tt(rom_base); + // Is the secure bit set? => secure mode + (tt & (1 << 22)) != 0 +} + +/// Determine if we are in secure mode +/// +/// Always returns `false` on RISC-V as it is impossible to determine if +/// you are in Machine Mode or User Mode by design. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub fn is_secure_mode() -> bool { + false +} diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index 1617c144c..b89df74a2 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -106,15 +106,55 @@ impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { if let Some(pin) = &clk { pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); } if let Some(pin) = &mosi { pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); } if let Some(pin) = &miso { pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); } if let Some(pin) = &cs { pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); } Self { inner, @@ -442,8 +482,8 @@ impl<'d, T: Instance> Spi<'d, T, Async> { trait SealedMode {} trait SealedInstance { - const TX_DREQ: u8; - const RX_DREQ: u8; + const TX_DREQ: pac::dma::vals::TreqSel; + const RX_DREQ: pac::dma::vals::TreqSel; fn regs(&self) -> pac::spi::Spi; } @@ -459,8 +499,8 @@ pub trait Instance: SealedInstance {} macro_rules! impl_instance { ($type:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { impl SealedInstance for peripherals::$type { - const TX_DREQ: u8 = $tx_dreq; - const RX_DREQ: u8 = $rx_dreq; + const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq; + const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq; fn regs(&self) -> pac::spi::Spi { pac::$type @@ -470,8 +510,18 @@ macro_rules! impl_instance { }; } -impl_instance!(SPI0, Spi0, 16, 17); -impl_instance!(SPI1, Spi1, 18, 19); +impl_instance!( + SPI0, + Spi0, + pac::dma::vals::TreqSel::SPI0_TX, + pac::dma::vals::TreqSel::SPI0_RX +); +impl_instance!( + SPI1, + Spi1, + pac::dma::vals::TreqSel::SPI1_TX, + pac::dma::vals::TreqSel::SPI1_RX +); /// CLK pin. pub trait ClkPin: GpioPin {} @@ -518,6 +568,42 @@ impl_pin!(PIN_26, SPI1, ClkPin); impl_pin!(PIN_27, SPI1, MosiPin); impl_pin!(PIN_28, SPI1, MisoPin); impl_pin!(PIN_29, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, SPI1, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, SPI0, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, SPI0, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, SPI0, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, SPI0, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, SPI0, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, SPI0, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, SPI0, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, SPI0, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, SPI1, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, SPI1, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, SPI1, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, SPI1, MosiPin); macro_rules! impl_mode { ($name:ident) => { diff --git a/embassy-rp/src/time_driver.rs b/embassy-rp/src/time_driver.rs index bab1044cb..e5b407a29 100644 --- a/embassy-rp/src/time_driver.rs +++ b/embassy-rp/src/time_driver.rs @@ -6,6 +6,10 @@ use critical_section::CriticalSection; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::Mutex; use embassy_time_driver::{AlarmHandle, Driver}; +#[cfg(feature = "rp2040")] +use pac::TIMER; +#[cfg(feature = "_rp235x")] +use pac::TIMER0 as TIMER; use crate::interrupt::InterruptExt; use crate::{interrupt, pac}; @@ -35,9 +39,9 @@ embassy_time_driver::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{ impl Driver for TimerDriver { fn now(&self) -> u64 { loop { - let hi = pac::TIMER.timerawh().read(); - let lo = pac::TIMER.timerawl().read(); - let hi2 = pac::TIMER.timerawh().read(); + let hi = TIMER.timerawh().read(); + let lo = TIMER.timerawl().read(); + let hi2 = TIMER.timerawh().read(); if hi == hi2 { return (hi as u64) << 32 | (lo as u64); } @@ -77,13 +81,13 @@ impl Driver for TimerDriver { // Note that we're not checking the high bits at all. This means the irq may fire early // if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire // it is checked if the alarm time has passed. - pac::TIMER.alarm(n).write_value(timestamp as u32); + TIMER.alarm(n).write_value(timestamp as u32); let now = self.now(); if timestamp <= now { // If alarm timestamp has passed the alarm will not fire. // Disarm the alarm and return `false` to indicate that. - pac::TIMER.armed().write(|w| w.set_armed(1 << n)); + TIMER.armed().write(|w| w.set_armed(1 << n)); alarm.timestamp.set(u64::MAX); @@ -105,17 +109,17 @@ impl TimerDriver { } else { // Not elapsed, arm it again. // This can happen if it was set more than 2^32 us in the future. - pac::TIMER.alarm(n).write_value(timestamp as u32); + TIMER.alarm(n).write_value(timestamp as u32); } }); // clear the irq - pac::TIMER.intr().write(|w| w.set_alarm(n, true)); + TIMER.intr().write(|w| w.set_alarm(n, true)); } fn trigger_alarm(&self, n: usize, cs: CriticalSection) { // disarm - pac::TIMER.armed().write(|w| w.set_armed(1 << n)); + TIMER.armed().write(|w| w.set_armed(1 << n)); let alarm = &self.alarms.borrow(cs)[n]; alarm.timestamp.set(u64::MAX); @@ -138,38 +142,72 @@ pub unsafe fn init() { }); // enable all irqs - pac::TIMER.inte().write(|w| { + TIMER.inte().write(|w| { w.set_alarm(0, true); w.set_alarm(1, true); w.set_alarm(2, true); w.set_alarm(3, true); }); - interrupt::TIMER_IRQ_0.enable(); - interrupt::TIMER_IRQ_1.enable(); - interrupt::TIMER_IRQ_2.enable(); - interrupt::TIMER_IRQ_3.enable(); + #[cfg(feature = "rp2040")] + { + interrupt::TIMER_IRQ_0.enable(); + interrupt::TIMER_IRQ_1.enable(); + interrupt::TIMER_IRQ_2.enable(); + interrupt::TIMER_IRQ_3.enable(); + } + #[cfg(feature = "_rp235x")] + { + interrupt::TIMER0_IRQ_0.enable(); + interrupt::TIMER0_IRQ_1.enable(); + interrupt::TIMER0_IRQ_2.enable(); + interrupt::TIMER0_IRQ_3.enable(); + } } -#[cfg(feature = "rt")] +#[cfg(all(feature = "rt", feature = "rp2040"))] #[interrupt] fn TIMER_IRQ_0() { DRIVER.check_alarm(0) } -#[cfg(feature = "rt")] +#[cfg(all(feature = "rt", feature = "rp2040"))] #[interrupt] fn TIMER_IRQ_1() { DRIVER.check_alarm(1) } -#[cfg(feature = "rt")] +#[cfg(all(feature = "rt", feature = "rp2040"))] #[interrupt] fn TIMER_IRQ_2() { DRIVER.check_alarm(2) } -#[cfg(feature = "rt")] +#[cfg(all(feature = "rt", feature = "rp2040"))] #[interrupt] fn TIMER_IRQ_3() { DRIVER.check_alarm(3) } + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +fn TIMER0_IRQ_0() { + DRIVER.check_alarm(0) +} + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +fn TIMER0_IRQ_1() { + DRIVER.check_alarm(1) +} + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +fn TIMER0_IRQ_2() { + DRIVER.check_alarm(2) +} + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +fn TIMER0_IRQ_3() { + DRIVER.check_alarm(3) +} diff --git a/embassy-rp/src/trng.rs b/embassy-rp/src/trng.rs new file mode 100644 index 000000000..9f2f33c4b --- /dev/null +++ b/embassy-rp/src/trng.rs @@ -0,0 +1,405 @@ +//! True Random Number Generator (TRNG) driver. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::Not; +use core::task::Poll; + +use embassy_hal_internal::Peripheral; +use embassy_sync::waitqueue::AtomicWaker; +use rand_core::Error; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::peripherals::TRNG; +use crate::{interrupt, pac}; + +trait SealedInstance { + fn regs() -> pac::trng::Trng; + fn waker() -> &'static AtomicWaker; +} + +/// TRNG peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +impl SealedInstance for TRNG { + fn regs() -> rp_pac::trng::Trng { + pac::TRNG + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } +} + +impl Instance for TRNG { + type Interrupt = interrupt::typelevel::TRNG_IRQ; +} + +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +/// TRNG ROSC Inverter chain length options. +pub enum InverterChainLength { + None = 0, + One, + Two, + Three, + Four, +} + +impl From for u8 { + fn from(value: InverterChainLength) -> Self { + value as u8 + } +} + +/// Configuration for the TRNG. +/// +/// - Three built in entropy checks +/// - ROSC frequency controlled by selecting one of ROSC chain lengths +/// - Sample period in terms of system clock ticks +/// +/// +/// Default configuration is based on the following from documentation: +/// +/// ---- +/// +/// RP2350 Datasheet 12.12.2 +/// +/// ... +/// +/// When configuring the TRNG block, consider the following principles: +/// • As average generation time increases, result quality increases and failed entropy checks decrease. +/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and +/// failed entropy checks. +/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or +/// 1 and sample count settings of 20-25. +/// +/// --- +/// +/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly +/// by setting the sample period to 0. Random data collected this way is then passed through +/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!). +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Bypass TRNG autocorrelation test + pub disable_autocorrelation_test: bool, + /// Bypass CRNGT test + pub disable_crngt_test: bool, + /// When set, the Von-Neuman balancer is bypassed (including the + /// 32 consecutive bits test) + pub disable_von_neumann_balancer: bool, + /// Sets the number of rng_clk cycles between two consecutive + /// ring oscillator samples. + /// Note: If the von Neumann decorrelator is bypassed, the minimum value for + /// sample counter must not be less than seventeen + pub sample_count: u32, + /// Selects the number of inverters (out of four possible + /// selections) in the ring oscillator (the entropy source). Higher values select + /// longer inverter chain lengths. + pub inverter_chain_length: InverterChainLength, +} + +impl Default for Config { + fn default() -> Self { + Config { + disable_autocorrelation_test: true, + disable_crngt_test: true, + disable_von_neumann_balancer: true, + sample_count: 25, + inverter_chain_length: InverterChainLength::One, + } + } +} + +/// True Random Number Generator Driver for RP2350 +/// +/// This driver provides async and blocking options. +/// +/// See [Config] for configuration details. +/// +/// Usage example: +/// ```no_run +/// use embassy_executor::Spawner; +/// use embassy_rp::trng::Trng; +/// use embassy_rp::peripherals::TRNG; +/// use embassy_rp::bind_interrupts; +/// +/// bind_interrupts!(struct Irqs { +/// TRNG_IRQ => embassy_rp::trng::InterruptHandler; +/// }); +/// +/// #[embassy_executor::main] +/// async fn main(spawner: Spawner) { +/// let peripherals = embassy_rp::init(Default::default()); +/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); +/// +/// let mut randomness = [0u8; 58]; +/// loop { +/// trng.fill_bytes(&mut randomness).await; +/// assert_ne!(randomness, [0u8; 58]); +/// } +///} +/// ``` +pub struct Trng<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, +} + +/// 12.12.1. Overview +/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of +/// periodic samples from the TRNG block’s internal Ring Oscillator (ROSC). +const TRNG_BLOCK_SIZE_BITS: usize = 192; +const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8; + +impl<'d, T: Instance> Trng<'d, T> { + /// Create a new TRNG driver. + pub fn new( + _trng: impl Peripheral

+ 'd, + _irq: impl Binding> + 'd, + config: Config, + ) -> Self { + let regs = T::regs(); + + regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false)); + + let trng_config_register = regs.trng_config(); + trng_config_register.write(|w| { + w.set_rnd_src_sel(config.inverter_chain_length.clone().into()); + }); + + let sample_count_register = regs.sample_cnt1(); + sample_count_register.write(|w| { + *w = config.sample_count; + }); + + let debug_control_register = regs.trng_debug_control(); + debug_control_register.write(|w| { + w.set_auto_correlate_bypass(config.disable_autocorrelation_test); + w.set_trng_crngt_bypass(config.disable_crngt_test); + w.set_vnc_bypass(config.disable_von_neumann_balancer) + }); + + Trng { phantom: PhantomData } + } + + fn start_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + // Enable TRNG ROSC + source_enable_register.write(|w| w.set_rnd_src_en(true)); + } + + fn stop_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + source_enable_register.write(|w| w.set_rnd_src_en(false)); + let reset_bits_counter_register = regs.rst_bits_counter(); + reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true)); + } + + fn enable_irq(&self) { + unsafe { T::Interrupt::enable() } + } + + fn disable_irq(&self) { + T::Interrupt::disable(); + } + + fn blocking_wait_for_successful_generation(&self) { + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let mut success = false; + while success.not() { + while trng_busy_register.read().trng_busy() {} + if trng_valid_register.read().ehr_valid().not() { + if regs.rng_isr().read().autocorr_err() { + regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true)); + } else { + panic!("RNG not busy, but ehr is not valid!") + } + } else { + success = true + } + } + } + + fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + let regs = T::regs(); + let ehr_data_regs = [ + regs.ehr_data0(), + regs.ehr_data1(), + regs.ehr_data2(), + regs.ehr_data3(), + regs.ehr_data4(), + regs.ehr_data5(), + ]; + + for (i, reg) in ehr_data_regs.iter().enumerate() { + buffer[i * 4..i * 4 + 4].copy_from_slice(®.read().to_ne_bytes()); + } + } + + fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + self.blocking_wait_for_successful_generation(); + self.read_ehr_registers_into_array(buffer); + } + + /// Fill the buffer with random bytes, async version. + pub async fn fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + + self.start_rng(); + self.enable_irq(); + + let mut bytes_transferred = 0usize; + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let waker = T::waker(); + + let destination_length = destination.len(); + + poll_fn(|context| { + waker.register(context.waker()); + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + if trng_busy_register.read().trng_busy() { + Poll::Pending + } else { + if trng_valid_register.read().ehr_valid().not() { + panic!("RNG not busy, but ehr is not valid!") + } + self.read_ehr_registers_into_array(&mut buffer); + let remaining = destination_length - bytes_transferred; + if remaining > TRNG_BLOCK_SIZE_BYTES { + destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES] + .copy_from_slice(&buffer); + bytes_transferred += TRNG_BLOCK_SIZE_BYTES + } else { + destination[bytes_transferred..bytes_transferred + remaining] + .copy_from_slice(&buffer[0..remaining]); + bytes_transferred += remaining + } + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + }) + .await + } + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + self.start_rng(); + + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) { + self.blocking_wait_for_successful_generation(); + self.blocking_read_ehr_registers_into_array(&mut buffer); + chunk.copy_from_slice(&buffer[..chunk.len()]) + } + self.stop_rng() + } + + /// Return a random u32, blocking. + pub fn blocking_next_u32(&mut self) -> u32 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = regs.ehr_data5().read(); + self.stop_rng(); + result + } + + /// Return a random u64, blocking. + pub fn blocking_next_u64(&mut self) -> u64 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + + let low = regs.ehr_data4().read() as u64; + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = (regs.ehr_data5().read() as u64) << 32 | low; + self.stop_rng(); + result + } +} + +impl<'d, T: Instance> rand_core::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} +/// TRNG interrupt handler. +pub struct InterruptHandler { + _trng: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let isr = regs.rng_isr().read(); + // Clear ehr bit + regs.rng_icr().write(|w| { + w.set_ehr_valid(true); + }); + if isr.ehr_valid() { + T::waker().wake(); + } else { + // 12.12.5. List of Registers + // ... + // TRNG: RNG_ISR Register + // ... + // AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row. + // When set, RNG ceases functioning until next reset + if isr.autocorr_err() { + warn!("TRNG Autocorrect error! Resetting TRNG"); + regs.trng_sw_reset().write(|w| { + w.set_trng_sw_reset(true); + }); + } + } + } +} diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index f546abe71..c94e5e185 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -224,6 +224,17 @@ impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { } impl<'d, T: Instance> UartTx<'d, T, Blocking> { + /// Create a new UART TX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(None) + } + /// Convert this uart TX instance into a buffered uart using the provided /// irq and transmit buffer. pub fn into_buffered( @@ -247,7 +258,7 @@ impl<'d, T: Instance> UartTx<'d, T, Async> { }); // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ) + crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ.into()) }; transfer.await; Ok(()) @@ -422,7 +433,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { 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) + crate::dma::read(ch, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ.into()) }; // wait for either the transfer to complete or an error to happen. @@ -490,6 +501,36 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { /// * The first call to `read_to_break()` will return `Ok(20)`. /// * The next call to `read_to_break()` will work as expected pub async fn read_to_break(&mut self, buffer: &mut [u8]) -> Result { + self.read_to_break_with_count(buffer, 0).await + } + + /// Read from the UART, waiting for a line break as soon as at least `min_count` bytes have been read. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n > min_count` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// If a line break occurs before `min_count` bytes have been read, the break will be ignored and the read will continue + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + line break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" line break + /// * If you expect a message of 20 bytes + line break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + pub async fn read_to_break_with_count( + &mut self, + buffer: &mut [u8], + min_count: usize, + ) -> 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); @@ -502,7 +543,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // then drain the fifo. we need to read at most 32 bytes. errors that apply // to fifo bytes will be reported directly. - let sbuffer = match { + let mut sbuffer = match { let limit = buffer.len().min(32); self.drain_fifo(&mut buffer[0..limit]) } { @@ -511,7 +552,13 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { // Drained (some/all of the fifo), no room left Ok(len) => return Err(ReadToBreakError::MissingBreak(len)), // We got a break WHILE draining the FIFO, return what we did get before the break - Err((i, Error::Break)) => return Ok(i), + Err((len, Error::Break)) => { + if len < min_count && len < buffer.len() { + &mut buffer[len..] + } else { + return Ok(len); + } + } // Some other error, just return the error Err((_i, e)) => return Err(ReadToBreakError::Other(e)), }; @@ -530,110 +577,123 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { reg.set_rxdmae(true); reg.set_dmaonerr(true); }); - let transfer = unsafe { - // If we don't assign future to a variable, the data register pointer - // is held across an await and makes the future non-Send. - crate::dma::read(&mut ch, T::regs().uartdr().as_ptr() as *const _, sbuffer, T::RX_DREQ) - }; - // wait for either the transfer to complete or an error to happen. - let transfer_result = select( - transfer, - poll_fn(|cx| { - T::dma_state().rx_err_waker.register(cx.waker()); - match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { - 0 => Poll::Pending, - e => Poll::Ready(Uartris(e as u32)), + loop { + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read( + &mut ch, + T::regs().uartdr().as_ptr() as *const _, + sbuffer, + T::RX_DREQ.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) { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + // Figure out our error state + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) } - }), - ) - .await; - - // Figure out our error state - let errors = match transfer_result { - Either::First(()) => { - // We're here because the DMA finished, BUT if an error occurred on the LAST - // byte, then we may still need to grab the error state! - Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) - } - Either::Second(e) => { - // We're here because we errored, which means this is the error that - // was problematic. - e - } - }; - - if errors.0 == 0 { - // No errors? That means we filled the buffer without a line break. - // For THIS function, that's a problem. - return Err(ReadToBreakError::MissingBreak(buffer.len())); - } else if errors.beris() { - // We got a Line Break! By this point, we've finished/aborted the DMA - // transaction, which means that we need to figure out where it left off - // by looking at the write_addr. - // - // First, we do a sanity check to make sure the write value is within the - // range of DMA we just did. - let sval = buffer.as_ptr() as usize; - let eval = sval + buffer.len(); - - // This is the address where the DMA would write to next - let next_addr = ch.regs().write_addr().read() as usize; - - // If we DON'T end up inside the range, something has gone really wrong. - // Note that it's okay that `eval` is one past the end of the slice, as - // this is where the write pointer will end up at the end of a full - // transfer. - if (next_addr < sval) || (next_addr > eval) { - unreachable!("UART DMA reported invalid `write_addr`"); - } - - let regs = T::regs(); - let all_full = next_addr == eval; - - // NOTE: This is off label usage of RSR! See the issue below for - // why I am not checking if there is an "extra" FIFO byte, and why - // I am checking RSR directly (it seems to report the status of the LAST - // POPPED value, rather than the NEXT TO POP value like the datasheet - // suggests!) - // - // issue: https://github.com/raspberrypi/pico-feedback/issues/367 - let last_was_break = regs.uartrsr().read().be(); - - return match (all_full, last_was_break) { - (true, true) | (false, _) => { - // We got less than the full amount + a break, or the full amount - // and the last byte was a break. Subtract the break off by adding one to sval. - Ok(next_addr.saturating_sub(1 + sval)) - } - (true, false) => { - // We finished the whole DMA, and the last DMA'd byte was NOT a break - // character. This is an error. - // - // NOTE: we COULD potentially return Ok(buffer.len()) here, since we - // know a line break occured at SOME POINT after the DMA completed. - // - // However, we have no way of knowing if there was extra data BEFORE - // that line break, so instead return an Err to signal to the caller - // that there are "leftovers", and they'll catch the actual line break - // on the next call. - // - // Doing it like this also avoids racyness: now whether you finished - // the full read BEFORE the line break occurred or AFTER the line break - // occurs, you still get `MissingBreak(buffer.len())` instead of sometimes - // getting `Ok(buffer.len())` if you were "late enough" to observe the - // line break. - Err(ReadToBreakError::MissingBreak(buffer.len())) + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e } }; - } else if errors.oeris() { - return Err(ReadToBreakError::Other(Error::Overrun)); - } else if errors.peris() { - return Err(ReadToBreakError::Other(Error::Parity)); - } else if errors.feris() { - return Err(ReadToBreakError::Other(Error::Framing)); + + if errors.0 == 0 { + // No errors? That means we filled the buffer without a line break. + // For THIS function, that's a problem. + return Err(ReadToBreakError::MissingBreak(buffer.len())); + } else if errors.beris() { + // We got a Line Break! By this point, we've finished/aborted the DMA + // transaction, which means that we need to figure out where it left off + // by looking at the write_addr. + // + // First, we do a sanity check to make sure the write value is within the + // range of DMA we just did. + let sval = buffer.as_ptr() as usize; + let eval = sval + buffer.len(); + + // This is the address where the DMA would write to next + let next_addr = ch.regs().write_addr().read() as usize; + + // If we DON'T end up inside the range, something has gone really wrong. + // Note that it's okay that `eval` is one past the end of the slice, as + // this is where the write pointer will end up at the end of a full + // transfer. + if (next_addr < sval) || (next_addr > eval) { + unreachable!("UART DMA reported invalid `write_addr`"); + } + + if (next_addr - sval) < min_count { + sbuffer = &mut buffer[(next_addr - sval)..]; + continue; + } + + let regs = T::regs(); + let all_full = next_addr == eval; + + // NOTE: This is off label usage of RSR! See the issue below for + // why I am not checking if there is an "extra" FIFO byte, and why + // I am checking RSR directly (it seems to report the status of the LAST + // POPPED value, rather than the NEXT TO POP value like the datasheet + // suggests!) + // + // issue: https://github.com/raspberrypi/pico-feedback/issues/367 + let last_was_break = regs.uartrsr().read().be(); + + return match (all_full, last_was_break) { + (true, true) | (false, _) => { + // We got less than the full amount + a break, or the full amount + // and the last byte was a break. Subtract the break off by adding one to sval. + Ok(next_addr.saturating_sub(1 + sval)) + } + (true, false) => { + // We finished the whole DMA, and the last DMA'd byte was NOT a break + // character. This is an error. + // + // NOTE: we COULD potentially return Ok(buffer.len()) here, since we + // know a line break occured at SOME POINT after the DMA completed. + // + // However, we have no way of knowing if there was extra data BEFORE + // that line break, so instead return an Err to signal to the caller + // that there are "leftovers", and they'll catch the actual line break + // on the next call. + // + // Doing it like this also avoids racyness: now whether you finished + // the full read BEFORE the line break occurred or AFTER the line break + // occurs, you still get `MissingBreak(buffer.len())` instead of sometimes + // getting `Ok(buffer.len())` if you were "late enough" to observe the + // line break. + Err(ReadToBreakError::MissingBreak(buffer.len())) + } + }; + } else if errors.oeris() { + return Err(ReadToBreakError::Other(Error::Overrun)); + } else if errors.peris() { + return Err(ReadToBreakError::Other(Error::Parity)); + } else if errors.feris() { + return Err(ReadToBreakError::Other(Error::Framing)); + } + unreachable!("unrecognized rx error"); } - unreachable!("unrecognized rx error"); } } @@ -786,26 +846,50 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { ) { let r = T::regs(); if let Some(pin) = &tx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if (pin_number % 4) == 0 { + 2 + } else { + 11 + } + }; pin.gpio().ctrl().write(|w| { - w.set_funcsel(2); + w.set_funcsel(funcsel); w.set_outover(if config.invert_tx { Outover::INVERT } else { Outover::NORMAL }); }); - pin.pad_ctrl().write(|w| w.set_ie(true)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); } if let Some(pin) = &rx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if ((pin_number - 1) % 4) == 0 { + 2 + } else { + 11 + } + }; pin.gpio().ctrl().write(|w| { - w.set_funcsel(2); + w.set_funcsel(funcsel); w.set_inover(if config.invert_rx { Inover::INVERT } else { Inover::NORMAL }); }); - pin.pad_ctrl().write(|w| w.set_ie(true)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); } if let Some(pin) = &cts { pin.gpio().ctrl().write(|w| { @@ -816,7 +900,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { Inover::NORMAL }); }); - pin.pad_ctrl().write(|w| w.set_ie(true)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); } if let Some(pin) = &rts { pin.gpio().ctrl().write(|w| { @@ -827,7 +915,11 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { Outover::NORMAL }); }); - pin.pad_ctrl().write(|w| w.set_ie(true)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); } Self::set_baudrate_inner(config.baudrate); @@ -860,7 +952,7 @@ impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { }); } - fn lcr_modify(f: impl FnOnce(&mut rp_pac::uart::regs::UartlcrH) -> R) -> R { + fn lcr_modify(f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { let r = T::regs(); // Notes from PL011 reference manual: @@ -997,6 +1089,17 @@ impl<'d, T: Instance> Uart<'d, T, Async> { pub async fn read_to_break<'a>(&mut self, buf: &'a mut [u8]) -> Result { self.rx.read_to_break(buf).await } + + /// Read until the buffer is full or a line break occurs after at least `min_count` bytes have been read. + /// + /// See [`UartRx::read_to_break_with_count()`] for more details + pub async fn read_to_break_with_count<'a>( + &mut self, + buf: &'a mut [u8], + min_count: usize, + ) -> Result { + self.rx.read_to_break_with_count(buf, min_count).await + } } impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { @@ -1277,3 +1380,92 @@ impl_pin!(PIN_26, UART1, CtsPin); impl_pin!(PIN_27, UART1, RtsPin); impl_pin!(PIN_28, UART0, TxPin); impl_pin!(PIN_29, UART0, RxPin); + +// Additional functions added by all 2350s +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_2, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_3, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_6, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_7, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_10, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_11, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_14, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_15, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_18, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_19, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_22, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_23, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_26, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_27, UART1, RxPin); + +// Additional pins added by larger 2350 packages. +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RtsPin); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RxPin); diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs index 512271ae4..20ef881f9 100644 --- a/embassy-rp/src/usb.rs +++ b/embassy-rp/src/usb.rs @@ -28,10 +28,10 @@ pub trait Instance: SealedInstance + 'static { impl crate::usb::SealedInstance for peripherals::USB { fn regs() -> pac::usb::Usb { - pac::USBCTRL_REGS + pac::USB } fn dpram() -> crate::pac::usb_dpram::UsbDpram { - pac::USBCTRL_DPRAM + pac::USB_DPRAM } } @@ -41,7 +41,7 @@ impl crate::usb::Instance for peripherals::USB { const EP_COUNT: usize = 16; const EP_MEMORY_SIZE: usize = 4096; -const EP_MEMORY: *mut u8 = pac::USBCTRL_DPRAM.as_ptr() as *mut u8; +const EP_MEMORY: *mut u8 = pac::USB_DPRAM.as_ptr() as *mut u8; const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; diff --git a/embassy-rp/src/watchdog.rs b/embassy-rp/src/watchdog.rs index 229a306fe..edd48e0e0 100644 --- a/embassy-rp/src/watchdog.rs +++ b/embassy-rp/src/watchdog.rs @@ -34,6 +34,7 @@ impl Watchdog { /// /// * `cycles` - Total number of tick cycles before the next tick is generated. /// It is expected to be the frequency in MHz of clk_ref. + #[cfg(feature = "rp2040")] pub fn enable_tick_generation(&mut self, cycles: u8) { let watchdog = pac::WATCHDOG; watchdog.tick().write(|w| { diff --git a/embassy-stm32-wpan/Cargo.toml b/embassy-stm32-wpan/Cargo.toml index 307b948c1..54dfd39a6 100644 --- a/embassy-stm32-wpan/Cargo.toml +++ b/embassy-stm32-wpan/Cargo.toml @@ -21,10 +21,10 @@ features = ["stm32wb55rg"] [dependencies] embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.1", path = "../embassy-time", optional = true } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -embassy-hal-internal = { version = "0.1.0", path = "../embassy-hal-internal" } -embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" } +embassy-embedded-hal = { version = "0.2.0", path = "../embassy-embedded-hal" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver", optional=true } defmt = { version = "0.3", optional = true } diff --git a/embassy-stm32-wpan/src/mac/commands.rs b/embassy-stm32-wpan/src/mac/commands.rs index 8f6dcbbbc..c97c609c3 100644 --- a/embassy-stm32-wpan/src/mac/commands.rs +++ b/embassy-stm32-wpan/src/mac/commands.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use core::{mem, slice}; use super::opcodes::OpcodeM4ToM0; diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 523bacb11..3c6484c96 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -43,15 +43,15 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.1", path = "../embassy-time", optional = true } +embassy-time = { version = "0.3.2", path = "../embassy-time", optional = true } embassy-time-driver = { version = "0.1", path = "../embassy-time-driver", optional = true } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } -embassy-hal-internal = {version = "0.1.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } -embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-hal-internal = {version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-4"] } +embassy-embedded-hal = {version = "0.2.0", path = "../embassy-embedded-hal" } embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver" } embassy-usb-synopsys-otg = {version = "0.1.0", path = "../embassy-usb-synopsys-otg" } -embassy-executor = { version = "0.5.0", path = "../embassy-executor", optional = true } +embassy-executor = { version = "0.6.0", path = "../embassy-executor", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } embedded-hal-1 = { package = "embedded-hal", version = "1.0" } @@ -72,7 +72,7 @@ rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" #stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e0cfd165fd8fffaa0df66a35eeca83b228496645" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364" } vcell = "0.1.3" nb = "1.0.0" @@ -80,7 +80,7 @@ stm32-fmc = "0.3.0" cfg-if = "1.0.0" embedded-io = { version = "0.6.0" } embedded-io-async = { version = "0.6.1" } -chrono = { version = "^0.4", default-features = false, optional = true} +chrono = { version = "^0.4", default-features = false, optional = true } bit_field = "0.10.2" document-features = "0.2.7" @@ -88,6 +88,8 @@ static_assertions = { version = "1.1" } volatile-register = { version = "0.2.1" } bitflags = "2.4.2" +block-device-driver = { version = "0.2" } +aligned = "0.4.1" [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } @@ -97,7 +99,7 @@ proc-macro2 = "1.0.36" quote = "1.0.15" #stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e0cfd165fd8fffaa0df66a35eeca83b228496645", default-features = false, features = ["metadata"] } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-ad00827345b4b758b2453082809d6e3b634b5364", default-features = false, features = ["metadata"] } [features] default = ["rt"] @@ -181,6 +183,9 @@ split-pc3 = ["_split-pins-enabled"] ## internal use only _split-pins-enabled = [] +## internal use only +_dual-core = [] + #! ## Chip-selection features #! Select your chip by specifying the model as a feature, e.g. `stm32c011d6`. #! Check the `Cargo.toml` for the latest list of supported chips. @@ -1004,40 +1009,40 @@ stm32h743xg = [ "stm32-metapac/stm32h743xg" ] stm32h743xi = [ "stm32-metapac/stm32h743xi" ] stm32h743zg = [ "stm32-metapac/stm32h743zg" ] stm32h743zi = [ "stm32-metapac/stm32h743zi" ] -stm32h745bg-cm7 = [ "stm32-metapac/stm32h745bg-cm7" ] -stm32h745bg-cm4 = [ "stm32-metapac/stm32h745bg-cm4" ] -stm32h745bi-cm7 = [ "stm32-metapac/stm32h745bi-cm7" ] -stm32h745bi-cm4 = [ "stm32-metapac/stm32h745bi-cm4" ] -stm32h745ig-cm7 = [ "stm32-metapac/stm32h745ig-cm7" ] -stm32h745ig-cm4 = [ "stm32-metapac/stm32h745ig-cm4" ] -stm32h745ii-cm7 = [ "stm32-metapac/stm32h745ii-cm7" ] -stm32h745ii-cm4 = [ "stm32-metapac/stm32h745ii-cm4" ] -stm32h745xg-cm7 = [ "stm32-metapac/stm32h745xg-cm7" ] -stm32h745xg-cm4 = [ "stm32-metapac/stm32h745xg-cm4" ] -stm32h745xi-cm7 = [ "stm32-metapac/stm32h745xi-cm7" ] -stm32h745xi-cm4 = [ "stm32-metapac/stm32h745xi-cm4" ] -stm32h745zg-cm7 = [ "stm32-metapac/stm32h745zg-cm7" ] -stm32h745zg-cm4 = [ "stm32-metapac/stm32h745zg-cm4" ] -stm32h745zi-cm7 = [ "stm32-metapac/stm32h745zi-cm7" ] -stm32h745zi-cm4 = [ "stm32-metapac/stm32h745zi-cm4" ] -stm32h747ag-cm7 = [ "stm32-metapac/stm32h747ag-cm7" ] -stm32h747ag-cm4 = [ "stm32-metapac/stm32h747ag-cm4" ] -stm32h747ai-cm7 = [ "stm32-metapac/stm32h747ai-cm7" ] -stm32h747ai-cm4 = [ "stm32-metapac/stm32h747ai-cm4" ] -stm32h747bg-cm7 = [ "stm32-metapac/stm32h747bg-cm7" ] -stm32h747bg-cm4 = [ "stm32-metapac/stm32h747bg-cm4" ] -stm32h747bi-cm7 = [ "stm32-metapac/stm32h747bi-cm7" ] -stm32h747bi-cm4 = [ "stm32-metapac/stm32h747bi-cm4" ] -stm32h747ig-cm7 = [ "stm32-metapac/stm32h747ig-cm7" ] -stm32h747ig-cm4 = [ "stm32-metapac/stm32h747ig-cm4" ] -stm32h747ii-cm7 = [ "stm32-metapac/stm32h747ii-cm7" ] -stm32h747ii-cm4 = [ "stm32-metapac/stm32h747ii-cm4" ] -stm32h747xg-cm7 = [ "stm32-metapac/stm32h747xg-cm7" ] -stm32h747xg-cm4 = [ "stm32-metapac/stm32h747xg-cm4" ] -stm32h747xi-cm7 = [ "stm32-metapac/stm32h747xi-cm7" ] -stm32h747xi-cm4 = [ "stm32-metapac/stm32h747xi-cm4" ] -stm32h747zi-cm7 = [ "stm32-metapac/stm32h747zi-cm7" ] -stm32h747zi-cm4 = [ "stm32-metapac/stm32h747zi-cm4" ] +stm32h745bg-cm7 = [ "stm32-metapac/stm32h745bg-cm7", "_dual-core" ] +stm32h745bg-cm4 = [ "stm32-metapac/stm32h745bg-cm4", "_dual-core" ] +stm32h745bi-cm7 = [ "stm32-metapac/stm32h745bi-cm7", "_dual-core" ] +stm32h745bi-cm4 = [ "stm32-metapac/stm32h745bi-cm4", "_dual-core" ] +stm32h745ig-cm7 = [ "stm32-metapac/stm32h745ig-cm7", "_dual-core" ] +stm32h745ig-cm4 = [ "stm32-metapac/stm32h745ig-cm4", "_dual-core" ] +stm32h745ii-cm7 = [ "stm32-metapac/stm32h745ii-cm7", "_dual-core" ] +stm32h745ii-cm4 = [ "stm32-metapac/stm32h745ii-cm4", "_dual-core" ] +stm32h745xg-cm7 = [ "stm32-metapac/stm32h745xg-cm7", "_dual-core" ] +stm32h745xg-cm4 = [ "stm32-metapac/stm32h745xg-cm4", "_dual-core" ] +stm32h745xi-cm7 = [ "stm32-metapac/stm32h745xi-cm7", "_dual-core" ] +stm32h745xi-cm4 = [ "stm32-metapac/stm32h745xi-cm4", "_dual-core" ] +stm32h745zg-cm7 = [ "stm32-metapac/stm32h745zg-cm7", "_dual-core" ] +stm32h745zg-cm4 = [ "stm32-metapac/stm32h745zg-cm4", "_dual-core" ] +stm32h745zi-cm7 = [ "stm32-metapac/stm32h745zi-cm7", "_dual-core" ] +stm32h745zi-cm4 = [ "stm32-metapac/stm32h745zi-cm4", "_dual-core" ] +stm32h747ag-cm7 = [ "stm32-metapac/stm32h747ag-cm7", "_dual-core" ] +stm32h747ag-cm4 = [ "stm32-metapac/stm32h747ag-cm4", "_dual-core" ] +stm32h747ai-cm7 = [ "stm32-metapac/stm32h747ai-cm7", "_dual-core" ] +stm32h747ai-cm4 = [ "stm32-metapac/stm32h747ai-cm4", "_dual-core" ] +stm32h747bg-cm7 = [ "stm32-metapac/stm32h747bg-cm7", "_dual-core" ] +stm32h747bg-cm4 = [ "stm32-metapac/stm32h747bg-cm4", "_dual-core" ] +stm32h747bi-cm7 = [ "stm32-metapac/stm32h747bi-cm7", "_dual-core" ] +stm32h747bi-cm4 = [ "stm32-metapac/stm32h747bi-cm4", "_dual-core" ] +stm32h747ig-cm7 = [ "stm32-metapac/stm32h747ig-cm7", "_dual-core" ] +stm32h747ig-cm4 = [ "stm32-metapac/stm32h747ig-cm4", "_dual-core" ] +stm32h747ii-cm7 = [ "stm32-metapac/stm32h747ii-cm7", "_dual-core" ] +stm32h747ii-cm4 = [ "stm32-metapac/stm32h747ii-cm4", "_dual-core" ] +stm32h747xg-cm7 = [ "stm32-metapac/stm32h747xg-cm7", "_dual-core" ] +stm32h747xg-cm4 = [ "stm32-metapac/stm32h747xg-cm4", "_dual-core" ] +stm32h747xi-cm7 = [ "stm32-metapac/stm32h747xi-cm7", "_dual-core" ] +stm32h747xi-cm4 = [ "stm32-metapac/stm32h747xi-cm4", "_dual-core" ] +stm32h747zi-cm7 = [ "stm32-metapac/stm32h747zi-cm7", "_dual-core" ] +stm32h747zi-cm4 = [ "stm32-metapac/stm32h747zi-cm4", "_dual-core" ] stm32h750ib = [ "stm32-metapac/stm32h750ib" ] stm32h750vb = [ "stm32-metapac/stm32h750vb" ] stm32h750xb = [ "stm32-metapac/stm32h750xb" ] @@ -1048,24 +1053,24 @@ stm32h753ii = [ "stm32-metapac/stm32h753ii" ] stm32h753vi = [ "stm32-metapac/stm32h753vi" ] stm32h753xi = [ "stm32-metapac/stm32h753xi" ] stm32h753zi = [ "stm32-metapac/stm32h753zi" ] -stm32h755bi-cm7 = [ "stm32-metapac/stm32h755bi-cm7" ] -stm32h755bi-cm4 = [ "stm32-metapac/stm32h755bi-cm4" ] -stm32h755ii-cm7 = [ "stm32-metapac/stm32h755ii-cm7" ] -stm32h755ii-cm4 = [ "stm32-metapac/stm32h755ii-cm4" ] -stm32h755xi-cm7 = [ "stm32-metapac/stm32h755xi-cm7" ] -stm32h755xi-cm4 = [ "stm32-metapac/stm32h755xi-cm4" ] -stm32h755zi-cm7 = [ "stm32-metapac/stm32h755zi-cm7" ] -stm32h755zi-cm4 = [ "stm32-metapac/stm32h755zi-cm4" ] -stm32h757ai-cm7 = [ "stm32-metapac/stm32h757ai-cm7" ] -stm32h757ai-cm4 = [ "stm32-metapac/stm32h757ai-cm4" ] -stm32h757bi-cm7 = [ "stm32-metapac/stm32h757bi-cm7" ] -stm32h757bi-cm4 = [ "stm32-metapac/stm32h757bi-cm4" ] -stm32h757ii-cm7 = [ "stm32-metapac/stm32h757ii-cm7" ] -stm32h757ii-cm4 = [ "stm32-metapac/stm32h757ii-cm4" ] -stm32h757xi-cm7 = [ "stm32-metapac/stm32h757xi-cm7" ] -stm32h757xi-cm4 = [ "stm32-metapac/stm32h757xi-cm4" ] -stm32h757zi-cm7 = [ "stm32-metapac/stm32h757zi-cm7" ] -stm32h757zi-cm4 = [ "stm32-metapac/stm32h757zi-cm4" ] +stm32h755bi-cm7 = [ "stm32-metapac/stm32h755bi-cm7", "_dual-core" ] +stm32h755bi-cm4 = [ "stm32-metapac/stm32h755bi-cm4", "_dual-core" ] +stm32h755ii-cm7 = [ "stm32-metapac/stm32h755ii-cm7", "_dual-core" ] +stm32h755ii-cm4 = [ "stm32-metapac/stm32h755ii-cm4", "_dual-core" ] +stm32h755xi-cm7 = [ "stm32-metapac/stm32h755xi-cm7", "_dual-core" ] +stm32h755xi-cm4 = [ "stm32-metapac/stm32h755xi-cm4", "_dual-core" ] +stm32h755zi-cm7 = [ "stm32-metapac/stm32h755zi-cm7", "_dual-core" ] +stm32h755zi-cm4 = [ "stm32-metapac/stm32h755zi-cm4", "_dual-core" ] +stm32h757ai-cm7 = [ "stm32-metapac/stm32h757ai-cm7", "_dual-core" ] +stm32h757ai-cm4 = [ "stm32-metapac/stm32h757ai-cm4", "_dual-core" ] +stm32h757bi-cm7 = [ "stm32-metapac/stm32h757bi-cm7", "_dual-core" ] +stm32h757bi-cm4 = [ "stm32-metapac/stm32h757bi-cm4", "_dual-core" ] +stm32h757ii-cm7 = [ "stm32-metapac/stm32h757ii-cm7", "_dual-core" ] +stm32h757ii-cm4 = [ "stm32-metapac/stm32h757ii-cm4", "_dual-core" ] +stm32h757xi-cm7 = [ "stm32-metapac/stm32h757xi-cm7", "_dual-core" ] +stm32h757xi-cm4 = [ "stm32-metapac/stm32h757xi-cm4", "_dual-core" ] +stm32h757zi-cm7 = [ "stm32-metapac/stm32h757zi-cm7", "_dual-core" ] +stm32h757zi-cm4 = [ "stm32-metapac/stm32h757zi-cm4", "_dual-core" ] stm32h7a3ag = [ "stm32-metapac/stm32h7a3ag" ] stm32h7a3ai = [ "stm32-metapac/stm32h7a3ai" ] stm32h7a3ig = [ "stm32-metapac/stm32h7a3ig" ] @@ -1598,14 +1603,14 @@ stm32wba55he = [ "stm32-metapac/stm32wba55he" ] stm32wba55hg = [ "stm32-metapac/stm32wba55hg" ] stm32wba55ue = [ "stm32-metapac/stm32wba55ue" ] stm32wba55ug = [ "stm32-metapac/stm32wba55ug" ] -stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4" ] -stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p" ] -stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4" ] -stm32wl54jc-cm0p = [ "stm32-metapac/stm32wl54jc-cm0p" ] -stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4" ] -stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p" ] -stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4" ] -stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p" ] +stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4", "_dual-core" ] +stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p", "_dual-core" ] +stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4", "_dual-core" ] +stm32wl54jc-cm0p = [ "stm32-metapac/stm32wl54jc-cm0p", "_dual-core" ] +stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4", "_dual-core" ] +stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p", "_dual-core" ] +stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4", "_dual-core" ] +stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p", "_dual-core" ] stm32wle4c8 = [ "stm32-metapac/stm32wle4c8" ] stm32wle4cb = [ "stm32-metapac/stm32wle4cb" ] stm32wle4cc = [ "stm32-metapac/stm32wle4cc" ] diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 8457e3a13..19cf193d9 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -480,7 +480,7 @@ fn main() { self.clock_names.insert(name.to_ascii_lowercase()); quote!(unsafe { unwrap!( - crate::rcc::get_freqs().#clock_name, + crate::rcc::get_freqs().#clock_name.to_hertz(), "peripheral '{}' is configured to use the '{}' clock, which is not running. \ Either enable it in 'config.rcc' or change 'config.rcc.mux' to use another clock", #peripheral, @@ -713,9 +713,10 @@ fn main() { g.extend(quote! { #[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[repr(C)] pub struct Clocks { #( - pub #clock_idents: Option, + pub #clock_idents: crate::time::MaybeHertz, )* } }); @@ -732,7 +733,7 @@ fn main() { $($(#[$m])* $k: $v,)* }; crate::rcc::set_freqs(crate::rcc::Clocks { - #( #clock_idents: all.#clock_idents, )* + #( #clock_idents: all.#clock_idents.into(), )* }); } }; @@ -1014,6 +1015,9 @@ fn main() { (("hrtim", "CHE2"), quote!(crate::hrtim::ChannelEComplementaryPin)), (("hrtim", "CHF1"), quote!(crate::hrtim::ChannelFPin)), (("hrtim", "CHF2"), quote!(crate::hrtim::ChannelFComplementaryPin)), + (("lptim", "CH1"), quote!(crate::lptim::Channel1Pin)), + (("lptim", "CH2"), quote!(crate::lptim::Channel2Pin)), + (("lptim", "OUT"), quote!(crate::lptim::OutputPin)), (("sdmmc", "CK"), quote!(crate::sdmmc::CkPin)), (("sdmmc", "CMD"), quote!(crate::sdmmc::CmdPin)), (("sdmmc", "D0"), quote!(crate::sdmmc::D0Pin)), @@ -1493,6 +1497,36 @@ fn main() { .flat_map(|p| &p.registers) .any(|p| p.kind == "dmamux"); + let mut dma_irqs: BTreeMap<&str, Vec> = BTreeMap::new(); + + for p in METADATA.peripherals { + if let Some(r) = &p.registers { + if r.kind == "dma" || r.kind == "bdma" || r.kind == "gpdma" || r.kind == "lpdma" { + for irq in p.interrupts { + let ch_name = format!("{}_{}", p.name, irq.signal); + let ch = METADATA.dma_channels.iter().find(|c| c.name == ch_name).unwrap(); + + // Some H7 chips have BDMA1 hardcoded for DFSDM, ie no DMAMUX. It's unsupported, skip it. + if has_dmamux && ch.dmamux.is_none() { + continue; + } + + dma_irqs.entry(irq.interrupt).or_default().push(ch_name); + } + } + } + } + + #[cfg(feature = "_dual-core")] + let mut dma_ch_to_irq: BTreeMap<&str, Vec> = BTreeMap::new(); + + #[cfg(feature = "_dual-core")] + for (irq, channels) in &dma_irqs { + for channel in channels { + dma_ch_to_irq.entry(channel).or_default().push(irq.to_string()); + } + } + for (ch_idx, ch) in METADATA.dma_channels.iter().enumerate() { // Some H7 chips have BDMA1 hardcoded for DFSDM, ie no DMAMUX. It's unsupported, skip it. if has_dmamux && ch.dmamux.is_none() { @@ -1501,6 +1535,16 @@ fn main() { let name = format_ident!("{}", ch.name); let idx = ch_idx as u8; + #[cfg(feature = "_dual-core")] + let irq = { + let irq_name = if let Some(x) = &dma_ch_to_irq.get(ch.name) { + format_ident!("{}", x.get(0).unwrap()) + } else { + panic!("failed to find dma interrupt") + }; + quote!(crate::pac::Interrupt::#irq_name) + }; + g.extend(quote!(dma_channel_impl!(#name, #idx);)); let dma = format_ident!("{}", ch.dma); @@ -1531,6 +1575,7 @@ fn main() { None => quote!(), }; + #[cfg(not(feature = "_dual-core"))] dmas.extend(quote! { crate::dma::ChannelInfo { dma: #dma_info, @@ -1538,31 +1583,20 @@ fn main() { #dmamux }, }); + #[cfg(feature = "_dual-core")] + dmas.extend(quote! { + crate::dma::ChannelInfo { + dma: #dma_info, + num: #ch_num, + irq: #irq, + #dmamux + }, + }); } // ======== // Generate DMA IRQs. - let mut dma_irqs: BTreeMap<&str, Vec> = BTreeMap::new(); - - for p in METADATA.peripherals { - if let Some(r) = &p.registers { - if r.kind == "dma" || r.kind == "bdma" || r.kind == "gpdma" { - for irq in p.interrupts { - let ch_name = format!("{}_{}", p.name, irq.signal); - let ch = METADATA.dma_channels.iter().find(|c| c.name == ch_name).unwrap(); - - // Some H7 chips have BDMA1 hardcoded for DFSDM, ie no DMAMUX. It's unsupported, skip it. - if has_dmamux && ch.dmamux.is_none() { - continue; - } - - dma_irqs.entry(irq.interrupt).or_default().push(ch_name); - } - } - } - } - let dma_irqs: TokenStream = dma_irqs .iter() .map(|(irq, channels)| { diff --git a/embassy-stm32/build_common.rs b/embassy-stm32/build_common.rs index 0487eb3c5..4f24e6d37 100644 --- a/embassy-stm32/build_common.rs +++ b/embassy-stm32/build_common.rs @@ -8,8 +8,6 @@ use std::collections::HashSet; use std::env; -use std::ffi::OsString; -use std::process::Command; /// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring /// them (`cargo:rust-check-cfg=cfg(X)`). @@ -17,7 +15,6 @@ use std::process::Command; pub struct CfgSet { enabled: HashSet, declared: HashSet, - emit_declared: bool, } impl CfgSet { @@ -25,7 +22,6 @@ impl CfgSet { Self { enabled: HashSet::new(), declared: HashSet::new(), - emit_declared: is_rustc_nightly(), } } @@ -49,7 +45,7 @@ impl CfgSet { /// /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. pub fn declare(&mut self, cfg: impl AsRef) { - if self.declared.insert(cfg.as_ref().to_owned()) && self.emit_declared { + if self.declared.insert(cfg.as_ref().to_owned()) { println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); } } @@ -69,21 +65,6 @@ impl CfgSet { } } -fn is_rustc_nightly() -> bool { - if env::var_os("EMBASSY_FORCE_CHECK_CFG").is_some() { - return true; - } - - let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); - - let output = Command::new(rustc) - .arg("--version") - .output() - .expect("failed to run `rustc --version`"); - - String::from_utf8_lossy(&output.stdout).contains("nightly") -} - /// Sets configs that describe the target platform. pub fn set_target_cfgs(cfgs: &mut CfgSet) { let target = env::var("TARGET").unwrap(); diff --git a/embassy-stm32/src/adc/f3.rs b/embassy-stm32/src/adc/f3.rs index ac88c9742..0ebeb8a9e 100644 --- a/embassy-stm32/src/adc/f3.rs +++ b/embassy-stm32/src/adc/f3.rs @@ -42,7 +42,7 @@ impl super::SealedAdcChannel for Vref { impl Vref { /// The value that vref would be if vdda was at 3300mv pub fn value(&self) -> u16 { - crate::pac::VREFINTCAL.data().read().value() + crate::pac::VREFINTCAL.data().read() } } diff --git a/embassy-stm32/src/adc/f3_v1_1.rs b/embassy-stm32/src/adc/f3_v1_1.rs index 689c2871d..291a3861e 100644 --- a/embassy-stm32/src/adc/f3_v1_1.rs +++ b/embassy-stm32/src/adc/f3_v1_1.rs @@ -74,7 +74,7 @@ impl super::SealedAdcChannel for Vref { impl Vref { /// The value that vref would be if vdda was at 3000mv pub fn calibrated_value(&self) -> u16 { - crate::pac::VREFINTCAL.data().read().value() + crate::pac::VREFINTCAL.data().read() } pub async fn calibrate(&mut self, adc: &mut Adc<'_, T>) -> Calibration { diff --git a/embassy-stm32/src/adc/g4.rs b/embassy-stm32/src/adc/g4.rs index c1e584f59..3e9ba8ae2 100644 --- a/embassy-stm32/src/adc/g4.rs +++ b/embassy-stm32/src/adc/g4.rs @@ -1,5 +1,9 @@ #[allow(unused)] +#[cfg(stm32h7)] use pac::adc::vals::{Adcaldif, Difsel, Exten}; +#[allow(unused)] +#[cfg(stm32g4)] +use pac::adc::vals::{Adcaldif, Difsel, Exten, Rovsm, Trovs}; use pac::adccommon::vals::Presc; use super::{blocking_delay_us, Adc, AdcChannel, Instance, Resolution, SampleTime}; @@ -228,6 +232,68 @@ impl<'d, T: Instance> Adc<'d, T> { Vbat {} } + /// Enable differential channel. + /// Caution: + /// : When configuring the channel “i” in differential input mode, its negative input voltage VINN[i] + /// is connected to another channel. As a consequence, this channel is no longer usable in + /// single-ended mode or in differential mode and must never be configured to be converted. + /// Some channels are shared between ADC1/ADC2/ADC3/ADC4/ADC5: this can make the + /// channel on the other ADC unusable. The only exception is when ADC master and the slave + /// operate in interleaved mode. + #[cfg(stm32g4)] + pub fn set_differential_channel(&mut self, ch: usize, enable: bool) { + T::regs().cr().modify(|w| w.set_aden(false)); // disable adc + T::regs().difsel().modify(|w| { + w.set_difsel( + ch, + if enable { + Difsel::DIFFERENTIAL + } else { + Difsel::SINGLEENDED + }, + ); + }); + T::regs().cr().modify(|w| w.set_aden(true)); + } + + #[cfg(stm32g4)] + pub fn set_differential(&mut self, channel: &mut impl AdcChannel, enable: bool) { + self.set_differential_channel(channel.channel() as usize, enable); + } + + /// Set oversampling shift. + #[cfg(stm32g4)] + pub fn set_oversampling_shift(&mut self, shift: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovss(shift)); + } + + /// Set oversampling ratio. + #[cfg(stm32g4)] + pub fn set_oversampling_ratio(&mut self, ratio: u8) { + T::regs().cfgr2().modify(|reg| reg.set_ovsr(ratio)); + } + + /// Enable oversampling in regular mode. + #[cfg(stm32g4)] + pub fn enable_regular_oversampling_mode(&mut self, mode: Rovsm, trig_mode: Trovs, enable: bool) { + T::regs().cfgr2().modify(|reg| reg.set_trovs(trig_mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovsm(mode)); + T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + } + + // Reads that are not implemented as INJECTED in "blocking_read" + // #[cfg(stm32g4)] + // pub fn enalble_injected_oversampling_mode(&mut self, enable: bool) { + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + + // #[cfg(stm32g4)] + // pub fn enable_oversampling_regular_injected_mode(&mut self, enable: bool) { + // // the regularoversampling mode is forced to resumed mode (ROVSM bit ignored), + // T::regs().cfgr2().modify(|reg| reg.set_rovse(enable)); + // T::regs().cfgr2().modify(|reg| reg.set_jovse(enable)); + // } + /// Set the ADC sample time. pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 7a7d7cd8e..4ab82c1d9 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -2,8 +2,9 @@ #![macro_use] #![allow(missing_docs)] // TODO +#![cfg_attr(adc_f3_v2, allow(unused))] -#[cfg(not(adc_f3_v2))] +#[cfg(not(any(adc_f3_v2, adc_u5)))] #[cfg_attr(adc_f1, path = "f1.rs")] #[cfg_attr(adc_f3, path = "f3.rs")] #[cfg_attr(adc_f3_v1_1, path = "f3_v1_1.rs")] @@ -18,13 +19,16 @@ mod _version; use core::marker::PhantomData; #[allow(unused)] -#[cfg(not(adc_f3_v2))] +#[cfg(not(any(adc_f3_v2, adc_u5)))] pub use _version::*; #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] use embassy_sync::waitqueue::AtomicWaker; -#[cfg(not(any(adc_f1, adc_f3_v2)))] +#[cfg(not(any(adc_u5)))] +pub use crate::pac::adc::vals; +#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))] pub use crate::pac::adc::vals::Res as Resolution; +#[cfg(not(any(adc_u5)))] pub use crate::pac::adc::vals::SampleTime; use crate::peripherals; @@ -34,7 +38,7 @@ dma_trait!(RxDma, Instance); pub struct Adc<'d, T: Instance> { #[allow(unused)] adc: crate::PeripheralRef<'d, T>, - #[cfg(not(any(adc_f3_v2, adc_f3_v1_1)))] + #[cfg(not(any(adc_f3_v2, adc_f3_v1_1, adc_u5)))] sample_time: SampleTime, } @@ -55,7 +59,7 @@ impl State { trait SealedInstance { #[allow(unused)] fn regs() -> crate::pac::adc::Adc; - #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))] + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))] #[allow(unused)] fn common_regs() -> crate::pac::adccommon::AdcCommon; #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] @@ -77,7 +81,7 @@ pub(crate) fn blocking_delay_us(us: u32) { embassy_time::block_for(embassy_time::Duration::from_micros(us as u64)); #[cfg(not(feature = "time"))] { - let freq = unsafe { crate::rcc::get_freqs() }.sys.unwrap().0 as u64; + let freq = unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 as u64; let us = us as u64; let cycles = freq * us / 1_000_000; cortex_m::asm::delay(cycles as u32); @@ -161,7 +165,7 @@ foreach_adc!( crate::pac::$inst } - #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))] + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0, adc_u5)))] fn common_regs() -> crate::pac::adccommon::AdcCommon { return crate::pac::$common_inst } @@ -198,7 +202,7 @@ macro_rules! impl_adc_pin { /// Get the maximum reading value for this resolution. /// /// This is `2**n - 1`. -#[cfg(not(any(adc_f1, adc_f3_v2)))] +#[cfg(not(any(adc_f1, adc_f3_v2, adc_u5)))] pub const fn resolution_to_max_count(res: Resolution) -> u32 { match res { #[cfg(adc_v4)] diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs index 278c93ff4..baa4bee79 100644 --- a/embassy-stm32/src/can/bxcan/mod.rs +++ b/embassy-stm32/src/can/bxcan/mod.rs @@ -68,7 +68,6 @@ pub struct SceInterruptHandler { impl interrupt::typelevel::Handler for SceInterruptHandler { unsafe fn on_interrupt() { - info!("sce irq"); let msr = T::regs().msr(); let msr_val = msr.read(); @@ -76,9 +75,8 @@ impl interrupt::typelevel::Handler for SceInterrup msr.modify(|m| m.set_slaki(true)); T::state().err_waker.wake(); } else if msr_val.erri() { - info!("Error interrupt"); // Disable the interrupt, but don't acknowledge the error, so that it can be - // forwarded off the the bus message consumer. If we don't provide some way for + // forwarded off the bus message consumer. If we don't provide some way for // downstream code to determine that it has already provided this bus error instance // to the bus message consumer, we are doomed to re-provide a single error instance for // an indefinite amount of time. diff --git a/embassy-stm32/src/dma/dma_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs index 8a6aa53a0..df041c4e9 100644 --- a/embassy-stm32/src/dma/dma_bdma.rs +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -15,6 +15,8 @@ use crate::{interrupt, pac}; pub(crate) struct ChannelInfo { pub(crate) dma: DmaInfo, pub(crate) num: usize, + #[cfg(feature = "_dual-core")] + pub(crate) irq: pac::Interrupt, #[cfg(dmamux)] pub(crate) dmamux: super::DmamuxInfo, } @@ -259,10 +261,12 @@ pub(crate) unsafe fn init( foreach_interrupt! { ($peri:ident, dma, $block:ident, $signal_name:ident, $irq:ident) => { crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, dma_priority); + #[cfg(not(feature = "_dual-core"))] crate::interrupt::typelevel::$irq::enable(); }; ($peri:ident, bdma, $block:ident, $signal_name:ident, $irq:ident) => { crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, bdma_priority); + #[cfg(not(feature = "_dual-core"))] crate::interrupt::typelevel::$irq::enable(); }; } @@ -341,6 +345,11 @@ impl AnyChannel { options: TransferOptions, ) { let info = self.info(); + #[cfg(feature = "_dual-core")] + { + use embassy_hal_internal::interrupt::InterruptExt as _; + info.irq.enable(); + } #[cfg(dmamux)] super::dmamux::configure_dmamux(&info.dmamux, _request); @@ -768,6 +777,7 @@ impl<'a, W: Word> ReadableRingBuffer<'a, W> { let dir = Dir::PeripheralToMemory; let data_size = W::size(); + options.half_transfer_ir = true; options.complete_transfer_ir = true; options.circular = true; diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index 13d5d15be..f9d66ca86 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -18,6 +18,8 @@ use crate::pac::gpdma::vals; pub(crate) struct ChannelInfo { pub(crate) dma: pac::gpdma::Gpdma, pub(crate) num: usize, + #[cfg(feature = "_dual-core")] + pub(crate) irq: pac::Interrupt, } /// GPDMA transfer options. @@ -57,6 +59,7 @@ pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: P foreach_interrupt! { ($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => { crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, irq_priority); + #[cfg(not(feature = "_dual-core"))] crate::interrupt::typelevel::$irq::enable(); }; } @@ -67,6 +70,12 @@ impl AnyChannel { /// Safety: Must be called with a matching set of parameters for a valid dma channel pub(crate) unsafe fn on_irq(&self) { let info = self.info(); + #[cfg(feature = "_dual-core")] + { + use embassy_hal_internal::interrupt::InterruptExt as _; + info.irq.enable(); + } + let state = &STATE[self.id as usize]; let ch = info.dma.ch(info.num); diff --git a/embassy-stm32/src/dsihost.rs b/embassy-stm32/src/dsihost.rs index 51f124542..77c3d95c3 100644 --- a/embassy-stm32/src/dsihost.rs +++ b/embassy-stm32/src/dsihost.rs @@ -14,7 +14,7 @@ pub fn blocking_delay_ms(ms: u32) { #[cfg(feature = "time")] embassy_time::block_for(embassy_time::Duration::from_millis(ms as u64)); #[cfg(not(feature = "time"))] - cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.unwrap().0 / 1_000 * ms); + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 1_000 * ms); } /// PacketTypes extracted from CubeMX diff --git a/embassy-stm32/src/flash/f1f3.rs b/embassy-stm32/src/flash/f1f3.rs index e66842e31..ff7f810ea 100644 --- a/embassy-stm32/src/flash/f1f3.rs +++ b/embassy-stm32/src/flash/f1f3.rs @@ -42,9 +42,11 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) // prevents parallelism errors fence(Ordering::SeqCst); + + wait_ready_blocking()?; } - wait_ready_blocking() + Ok(()) } pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { diff --git a/embassy-stm32/src/flash/f2.rs b/embassy-stm32/src/flash/f2.rs new file mode 100644 index 000000000..cdab1fd2d --- /dev/null +++ b/embassy-stm32/src/flash/f2.rs @@ -0,0 +1,142 @@ +use core::ptr::write_volatile; +use core::sync::atomic::{fence, AtomicBool, Ordering}; + +use pac::flash::regs::Sr; + +use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); + +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } +} + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.cr().read().lock() { + pac::FLASH.keyr().write_value(0x4567_0123); + pac::FLASH.keyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); + restore_data_cache_state(); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + blocking_wait_ready() +} + +unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(unwrap!(val.try_into()))); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); + + trace!("Blocking erasing sector number {}", sector.snb()); + + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.snb()) + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + clear_all_err(); + restore_data_cache_state(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.sr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + return get_result(sr); + } + } +} + +fn get_result(sr: Sr) -> Result<(), Error> { + if sr.pgserr() { + Err(Error::Seq) + } else if sr.pgperr() { + Err(Error::Parallelism) + } else if sr.pgaerr() { + Err(Error::Unaligned) + } else if sr.wrperr() { + Err(Error::Protected) + } else { + Ok(()) + } +} + +fn save_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Disable data cache during write/erase if there are two banks, see errata 2.2.12 + let dcen = pac::FLASH.acr().read().dcen(); + DATA_CACHE_WAS_ENABLED.store(dcen, Ordering::Relaxed); + if dcen { + pac::FLASH.acr().modify(|w| w.set_dcen(false)); + } + } +} + +fn restore_data_cache_state() { + let dual_bank = unwrap!(get_flash_regions().last()).bank == FlashBank::Bank2; + if dual_bank { + // Restore data cache if it was enabled + let dcen = DATA_CACHE_WAS_ENABLED.load(Ordering::Relaxed); + if dcen { + // Reset data cache before we enable it again + pac::FLASH.acr().modify(|w| w.set_dcrst(true)); + pac::FLASH.acr().modify(|w| w.set_dcrst(false)); + pac::FLASH.acr().modify(|w| w.set_dcen(true)) + } + } +} diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index ce2d1a04c..bce638db0 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -94,6 +94,7 @@ pub enum FlashBank { #[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_wl, flash_wb), path = "l.rs")] #[cfg_attr(flash_f0, path = "f0.rs")] #[cfg_attr(any(flash_f1, flash_f3), path = "f1f3.rs")] +#[cfg_attr(flash_f2, path = "f2.rs")] #[cfg_attr(flash_f4, path = "f4.rs")] #[cfg_attr(flash_f7, path = "f7.rs")] #[cfg_attr(any(flash_g0, flash_g4c2, flash_g4c3, flash_g4c4), path = "g.rs")] @@ -104,8 +105,8 @@ pub enum FlashBank { #[cfg_attr(flash_u0, path = "u0.rs")] #[cfg_attr( not(any( - flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f3, flash_f4, flash_f7, flash_g0, - flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0 + flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f2, flash_f3, flash_f4, flash_f7, + flash_g0, flash_g4c2, flash_g4c3, flash_g4c4, flash_h7, flash_h7ab, flash_u5, flash_h50, flash_u0 )), path = "other.rs" )] diff --git a/embassy-stm32/src/hsem/mod.rs b/embassy-stm32/src/hsem/mod.rs index b77a3415b..06ab7a9bc 100644 --- a/embassy-stm32/src/hsem/mod.rs +++ b/embassy-stm32/src/hsem/mod.rs @@ -158,6 +158,11 @@ impl<'d, T: Instance> HardwareSemaphore<'d, T> { .modify(|w| w.set_ise(sem_x, enable)); } + /// Gets the interrupt flag for the semaphore. + pub fn is_interrupt_active(&mut self, core_id: CoreId, sem_x: usize) -> bool { + T::regs().isr(core_id_to_index(core_id)).read().isf(sem_x) + } + /// Clears the interrupt flag for the semaphore. pub fn clear_interrupt(&mut self, core_id: CoreId, sem_x: usize) { T::regs() diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 95f59360a..451f595e0 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -89,6 +89,8 @@ pub mod i2s; pub mod ipcc; #[cfg(feature = "low-power")] pub mod low_power; +#[cfg(lptim)] +pub mod lptim; #[cfg(ltdc)] pub mod ltdc; #[cfg(opamp)] @@ -197,6 +199,7 @@ pub use crate::pac::NVIC_PRIO_BITS; /// `embassy-stm32` global configuration. #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { /// RCC config. pub rcc: rcc::Config, @@ -273,7 +276,137 @@ impl Default for Config { /// This returns the peripheral singletons that can be used for creating drivers. /// /// This should only be called once at startup, otherwise it panics. +#[cfg(not(feature = "_dual-core"))] pub fn init(config: Config) -> Peripherals { + init_hw(config) +} + +#[cfg(feature = "_dual-core")] +mod dual_core { + use core::cell::UnsafeCell; + use core::mem::MaybeUninit; + use core::sync::atomic::{AtomicUsize, Ordering}; + + use rcc::Clocks; + + use super::*; + + /// Object containing data that embassy needs to share between cores. + /// + /// It cannot be initialized by the user. The intended use is: + /// + /// ``` + /// #[link_section = ".ram_d3"] + /// static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + /// + /// init_secondary(&SHARED_DATA); + /// ``` + /// + /// This static must be placed in the same position for both cores. How and where this is done is left to the user. + pub struct SharedData { + init_flag: AtomicUsize, + clocks: UnsafeCell>, + config: UnsafeCell>, + } + + unsafe impl Sync for SharedData {} + + const INIT_DONE_FLAG: usize = 0xca11ab1e; + + /// Initialize the `embassy-stm32` HAL with the provided configuration. + /// This function does the actual initialization of the hardware, in contrast to [init_secondary] or [try_init_secondary]. + /// Any core can do the init, but it's important only one core does it. + /// + /// This returns the peripheral singletons that can be used for creating drivers. + /// + /// This should only be called once at startup, otherwise it panics. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn init_primary(config: Config, shared_data: &'static MaybeUninit) -> Peripherals { + let shared_data = unsafe { shared_data.assume_init_ref() }; + + rcc::set_freqs_ptr(shared_data.clocks.get()); + let p = init_hw(config); + + unsafe { *shared_data.config.get() }.write(config); + + shared_data.init_flag.store(INIT_DONE_FLAG, Ordering::SeqCst); + + p + } + + /// Try to initialize the `embassy-stm32` HAL based on the init done by the other core using [init_primary]. + /// + /// This returns the peripheral singletons that can be used for creating drivers if the other core is done with its init. + /// If the other core is not done yet, this will return `None`. + /// + /// This should only be called once at startup, otherwise it may panic. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn try_init_secondary(shared_data: &'static MaybeUninit) -> Option { + let shared_data = unsafe { shared_data.assume_init_ref() }; + + if shared_data.init_flag.load(Ordering::SeqCst) != INIT_DONE_FLAG { + return None; + } + + // Separate load and store to support the CM0 of the STM32WL + shared_data.init_flag.store(0, Ordering::SeqCst); + + Some(init_secondary_hw(shared_data)) + } + + /// Initialize the `embassy-stm32` HAL based on the init done by the other core using [init_primary]. + /// + /// This returns the peripheral singletons that can be used for creating drivers when the other core is done with its init. + /// If the other core is not done yet, this will spinloop wait on it. + /// + /// This should only be called once at startup, otherwise it may panic. + /// + /// The `shared_data` is used to coordinate the init with the second core. Read the [SharedData] docs + /// for more information on its requirements. + pub fn init_secondary(shared_data: &'static MaybeUninit) -> Peripherals { + loop { + if let Some(p) = try_init_secondary(shared_data) { + return p; + } + } + } + + fn init_secondary_hw(shared_data: &'static SharedData) -> Peripherals { + rcc::set_freqs_ptr(shared_data.clocks.get()); + + let config = unsafe { (*shared_data.config.get()).assume_init() }; + + // We use different timers on the different cores, so we have to still initialize one here + critical_section::with(|cs| { + unsafe { + dma::init( + cs, + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, + ) + } + + #[cfg(feature = "_time-driver")] + // must be after rcc init + time_driver::init(cs); + }); + + Peripherals::take() + } +} + +#[cfg(feature = "_dual-core")] +pub use dual_core::*; + +fn init_hw(config: Config) -> Peripherals { critical_section::with(|cs| { let p = Peripherals::take_with_cs(cs); diff --git a/embassy-stm32/src/low_power.rs b/embassy-stm32/src/low_power.rs index 604bdf416..f3e4c6994 100644 --- a/embassy-stm32/src/low_power.rs +++ b/embassy-stm32/src/low_power.rs @@ -109,10 +109,10 @@ pub enum StopMode { Stop2, } -#[cfg(stm32l5)] +#[cfg(any(stm32l4, stm32l5))] use stm32_metapac::pwr::vals::Lpms; -#[cfg(stm32l5)] +#[cfg(any(stm32l4, stm32l5))] impl Into for StopMode { fn into(self) -> Lpms { match self { @@ -181,7 +181,7 @@ impl Executor { #[allow(unused_variables)] fn configure_stop(&mut self, stop_mode: StopMode) { - #[cfg(stm32l5)] + #[cfg(any(stm32l4, stm32l5))] crate::pac::PWR.cr1().modify(|m| m.set_lpms(stop_mode.into())); #[cfg(stm32h5)] crate::pac::PWR.pmcr().modify(|v| { diff --git a/embassy-stm32/src/lptim/channel.rs b/embassy-stm32/src/lptim/channel.rs new file mode 100644 index 000000000..17fc2fb86 --- /dev/null +++ b/embassy-stm32/src/lptim/channel.rs @@ -0,0 +1,18 @@ +/// Timer channel. +#[derive(Clone, Copy)] +pub enum Channel { + /// Channel 1. + Ch1, + /// Channel 2. + Ch2, +} + +impl Channel { + /// Get the channel index (0..1) + pub fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, + } + } +} diff --git a/embassy-stm32/src/lptim/mod.rs b/embassy-stm32/src/lptim/mod.rs new file mode 100644 index 000000000..1649cc5b4 --- /dev/null +++ b/embassy-stm32/src/lptim/mod.rs @@ -0,0 +1,48 @@ +//! Low-power timer (LPTIM) + +pub mod pwm; +pub mod timer; + +use crate::rcc::RccPeripheral; + +/// Timer channel. +#[cfg(any(lptim_v2a, lptim_v2b))] +mod channel; +#[cfg(any(lptim_v2a, lptim_v2b))] +pub use channel::Channel; + +pin_trait!(OutputPin, BasicInstance); +pin_trait!(Channel1Pin, BasicInstance); +pin_trait!(Channel2Pin, BasicInstance); + +pub(crate) trait SealedInstance: RccPeripheral { + fn regs() -> crate::pac::lptim::Lptim; +} +pub(crate) trait SealedBasicInstance: RccPeripheral {} + +/// LPTIM basic instance trait. +#[allow(private_bounds)] +pub trait BasicInstance: SealedBasicInstance + 'static {} + +/// LPTIM instance trait. +#[allow(private_bounds)] +pub trait Instance: BasicInstance + SealedInstance + 'static {} + +foreach_interrupt! { + ($inst:ident, lptim, LPTIM, GLOBAL, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::lptim::Lptim { + crate::pac::$inst + } + } + impl SealedBasicInstance for crate::peripherals::$inst { + } + impl BasicInstance for crate::peripherals::$inst {} + impl Instance for crate::peripherals::$inst {} + }; + ($inst:ident, lptim, LPTIM_BASIC, GLOBAL, $irq:ident) => { + impl SealedBasicInstance for crate::peripherals::$inst { + } + impl BasicInstance for crate::peripherals::$inst {} + }; +} diff --git a/embassy-stm32/src/lptim/pwm.rs b/embassy-stm32/src/lptim/pwm.rs new file mode 100644 index 000000000..1f43eb6ee --- /dev/null +++ b/embassy-stm32/src/lptim/pwm.rs @@ -0,0 +1,168 @@ +//! PWM driver. + +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::timer::Timer; +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +use super::OutputPin; +#[cfg(any(lptim_v2a, lptim_v2b))] +use super::{channel::Channel, timer::ChannelDirection, Channel1Pin, Channel2Pin}; +use super::{BasicInstance, Instance}; +use crate::gpio::{AfType, AnyPin, OutputType, Speed}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Output marker type. +pub enum Output {} +/// Channel 1 marker type. +pub enum Ch1 {} +/// Channel 2 marker type. +pub enum Ch2 {} + +/// PWM pin wrapper. +/// +/// This wraps a pin to make it usable with PWM. +pub struct PwmPin<'d, T, C> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(T, C)>, +} + +macro_rules! channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident) => { + impl<'d, T: BasicInstance> PwmPin<'d, T, $channel> { + #[doc = concat!("Create a new ", stringify!($channel), " PWM pin instance.")] + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af( + pin.af_num(), + AfType::output(OutputType::PushPull, Speed::VeryHigh), + ); + }); + PwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +channel_impl!(new, Output, OutputPin); +#[cfg(any(lptim_v2a, lptim_v2b))] +channel_impl!(new_ch1, Ch1, Channel1Pin); +#[cfg(any(lptim_v2a, lptim_v2b))] +channel_impl!(new_ch2, Ch2, Channel2Pin); + +/// PWM driver. +pub struct Pwm<'d, T: Instance> { + inner: Timer<'d, T>, +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new PWM driver. + pub fn new(tim: impl Peripheral

+ 'd, _output_pin: PwmPin<'d, T, Output>, freq: Hertz) -> Self { + Self::new_inner(tim, freq) + } + + /// Set the duty. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, duty: u16) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(duty) + } + + /// Get the duty. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn get_duty(&self) -> u16 { + self.inner.get_compare_value() + } + + fn post_init(&mut self) {} +} + +#[cfg(any(lptim_v2a, lptim_v2b))] +impl<'d, T: Instance> Pwm<'d, T> { + /// Create a new PWM driver. + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1_pin: Option>, + _ch2_pin: Option>, + freq: Hertz, + ) -> Self { + Self::new_inner(tim, freq) + } + + /// Enable the given channel. + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + } + + /// Disable the given channel. + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self, channel: Channel) -> bool { + self.inner.get_channel_enable_state(channel) + } + + /// Set the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn set_duty(&mut self, channel: Channel, duty: u16) { + assert!(duty <= self.get_max_duty()); + self.inner.set_compare_value(channel, duty) + } + + /// Get the duty for a given channel. + /// + /// The value ranges from 0 for 0% duty, to [`get_max_duty`](Self::get_max_duty) for 100% duty, both included. + pub fn get_duty(&self, channel: Channel) -> u16 { + self.inner.get_compare_value(channel) + } + + fn post_init(&mut self) { + [Channel::Ch1, Channel::Ch2].iter().for_each(|&channel| { + self.inner.set_channel_direction(channel, ChannelDirection::OutputPwm); + }); + } +} + +impl<'d, T: Instance> Pwm<'d, T> { + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz) -> Self { + let mut this = Self { inner: Timer::new(tim) }; + + this.inner.enable(); + this.set_frequency(freq); + + this.post_init(); + + this.inner.continuous_mode_start(); + + this + } + + /// Set PWM frequency. + /// + /// Note: when you call this, the max duty value changes, so you will have to + /// call `set_duty` on all channels with the duty calculated based on the new max duty. + pub fn set_frequency(&mut self, frequency: Hertz) { + self.inner.set_frequency(frequency); + } + + /// Get max duty value. + /// + /// This value depends on the configured frequency and the timer's clock rate from RCC. + pub fn get_max_duty(&self) -> u16 { + self.inner.get_max_compare_value() + 1 + } +} diff --git a/embassy-stm32/src/lptim/timer/channel_direction.rs b/embassy-stm32/src/lptim/timer/channel_direction.rs new file mode 100644 index 000000000..a38df63cd --- /dev/null +++ b/embassy-stm32/src/lptim/timer/channel_direction.rs @@ -0,0 +1,18 @@ +use crate::pac::lptim::vals; + +/// Direction of a low-power timer channel +pub enum ChannelDirection { + /// Use channel as a PWM output + OutputPwm, + /// Use channel as an input capture + InputCapture, +} + +impl From for vals::Ccsel { + fn from(direction: ChannelDirection) -> Self { + match direction { + ChannelDirection::OutputPwm => vals::Ccsel::OUTPUTCOMPARE, + ChannelDirection::InputCapture => vals::Ccsel::INPUTCAPTURE, + } + } +} diff --git a/embassy-stm32/src/lptim/timer/mod.rs b/embassy-stm32/src/lptim/timer/mod.rs new file mode 100644 index 000000000..e62fcab49 --- /dev/null +++ b/embassy-stm32/src/lptim/timer/mod.rs @@ -0,0 +1,133 @@ +//! Low-level timer driver. +mod prescaler; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +#[cfg(any(lptim_v2a, lptim_v2b))] +use super::channel::Channel; +#[cfg(any(lptim_v2a, lptim_v2b))] +mod channel_direction; +#[cfg(any(lptim_v2a, lptim_v2b))] +pub use channel_direction::ChannelDirection; +use prescaler::Prescaler; + +use super::Instance; +use crate::rcc; +use crate::time::Hertz; + +/// Low-level timer driver. +pub struct Timer<'d, T: Instance> { + _tim: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Timer<'d, T> { + /// Create a new timer driver. + pub fn new(tim: impl Peripheral

+ 'd) -> Self { + into_ref!(tim); + + rcc::enable_and_reset::(); + + Self { _tim: tim } + } + + /// Enable the timer. + pub fn enable(&self) { + T::regs().cr().modify(|w| w.set_enable(true)); + } + + /// Disable the timer. + pub fn disable(&self) { + T::regs().cr().modify(|w| w.set_enable(false)); + } + + /// Start the timer in single pulse mode. + pub fn single_mode_start(&self) { + T::regs().cr().modify(|w| w.set_sngstrt(true)); + } + + /// Start the timer in continuous mode. + pub fn continuous_mode_start(&self) { + T::regs().cr().modify(|w| w.set_cntstrt(true)); + } + + /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. + pub fn set_frequency(&self, frequency: Hertz) { + let f = frequency.0; + assert!(f > 0); + + let pclk_f = T::frequency().0; + + let pclk_ticks_per_timer_period = pclk_f / f; + + let psc = Prescaler::from_ticks(pclk_ticks_per_timer_period); + let arr = psc.scale_down(pclk_ticks_per_timer_period); + + T::regs().cfgr().modify(|r| r.set_presc((&psc).into())); + T::regs().arr().modify(|r| r.set_arr(arr.into())); + } + + /// Get the timer frequency. + pub fn get_frequency(&self) -> Hertz { + let pclk_f = T::frequency(); + let arr = T::regs().arr().read().arr(); + let psc = Prescaler::from(T::regs().cfgr().read().presc()); + + pclk_f / psc.scale_up(arr) + } + + /// Get the clock frequency of the timer (before prescaler is applied). + pub fn get_clock_frequency(&self) -> Hertz { + T::frequency() + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + pub fn get_max_compare_value(&self) -> u16 { + T::regs().arr().read().arr() + } +} + +#[cfg(any(lptim_v2a, lptim_v2b))] +impl<'d, T: Instance> Timer<'d, T> { + /// Enable/disable a channel. + pub fn enable_channel(&self, channel: Channel, enable: bool) { + T::regs().ccmr(0).modify(|w| { + w.set_cce(channel.index(), enable); + }); + } + + /// Get enable/disable state of a channel + pub fn get_channel_enable_state(&self, channel: Channel) -> bool { + T::regs().ccmr(0).read().cce(channel.index()) + } + + /// Set compare value for a channel. + pub fn set_compare_value(&self, channel: Channel, value: u16) { + T::regs().ccr(channel.index()).modify(|w| w.set_ccr(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self, channel: Channel) -> u16 { + T::regs().ccr(channel.index()).read().ccr() + } + + /// Set channel direction. + #[cfg(any(lptim_v2a, lptim_v2b))] + pub fn set_channel_direction(&self, channel: Channel, direction: ChannelDirection) { + T::regs() + .ccmr(0) + .modify(|w| w.set_ccsel(channel.index(), direction.into())); + } +} + +#[cfg(not(any(lptim_v2a, lptim_v2b)))] +impl<'d, T: Instance> Timer<'d, T> { + /// Set compare value for a channel. + pub fn set_compare_value(&self, value: u16) { + T::regs().cmp().modify(|w| w.set_cmp(value)); + } + + /// Get compare value for a channel. + pub fn get_compare_value(&self) -> u16 { + T::regs().cmp().read().cmp() + } +} diff --git a/embassy-stm32/src/lptim/timer/prescaler.rs b/embassy-stm32/src/lptim/timer/prescaler.rs new file mode 100644 index 000000000..5d2326faf --- /dev/null +++ b/embassy-stm32/src/lptim/timer/prescaler.rs @@ -0,0 +1,90 @@ +//! Low-level timer driver. + +use crate::pac::lptim::vals; + +pub enum Prescaler { + Div1, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl From<&Prescaler> for vals::Presc { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => vals::Presc::DIV1, + Prescaler::Div2 => vals::Presc::DIV2, + Prescaler::Div4 => vals::Presc::DIV4, + Prescaler::Div8 => vals::Presc::DIV8, + Prescaler::Div16 => vals::Presc::DIV16, + Prescaler::Div32 => vals::Presc::DIV32, + Prescaler::Div64 => vals::Presc::DIV64, + Prescaler::Div128 => vals::Presc::DIV128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: vals::Presc) -> Self { + match prescaler { + vals::Presc::DIV1 => Prescaler::Div1, + vals::Presc::DIV2 => Prescaler::Div2, + vals::Presc::DIV4 => Prescaler::Div4, + vals::Presc::DIV8 => Prescaler::Div8, + vals::Presc::DIV16 => Prescaler::Div16, + vals::Presc::DIV32 => Prescaler::Div32, + vals::Presc::DIV64 => Prescaler::Div64, + vals::Presc::DIV128 => Prescaler::Div128, + } + } +} + +impl From<&Prescaler> for u32 { + fn from(prescaler: &Prescaler) -> Self { + match prescaler { + Prescaler::Div1 => 1, + Prescaler::Div2 => 2, + Prescaler::Div4 => 4, + Prescaler::Div8 => 8, + Prescaler::Div16 => 16, + Prescaler::Div32 => 32, + Prescaler::Div64 => 64, + Prescaler::Div128 => 128, + } + } +} + +impl From for Prescaler { + fn from(prescaler: u32) -> Self { + match prescaler { + 1 => Prescaler::Div1, + 2 => Prescaler::Div2, + 4 => Prescaler::Div4, + 8 => Prescaler::Div8, + 16 => Prescaler::Div16, + 32 => Prescaler::Div32, + 64 => Prescaler::Div64, + 128 => Prescaler::Div128, + _ => unreachable!(), + } + } +} + +impl Prescaler { + pub fn from_ticks(ticks: u32) -> Self { + // We need to scale down to a 16-bit range + (ticks >> 16).next_power_of_two().into() + } + + pub fn scale_down(&self, ticks: u32) -> u16 { + (ticks / u32::from(self)).try_into().unwrap() + } + + pub fn scale_up(&self, ticks: u16) -> u32 { + u32::from(self) * ticks as u32 + } +} diff --git a/embassy-stm32/src/opamp.rs b/embassy-stm32/src/opamp.rs index ca94a573d..c86c18e22 100644 --- a/embassy-stm32/src/opamp.rs +++ b/embassy-stm32/src/opamp.rs @@ -45,6 +45,7 @@ pub struct OpAmpOutput<'d, T: Instance> { /// OpAmp internal outputs, wired directly to ADC inputs. /// /// This struct can be used as an ADC input. +#[cfg(opamp_g4)] pub struct OpAmpInternalOutput<'d, T: Instance> { _inner: &'d OpAmp<'d, T>, } @@ -80,11 +81,11 @@ impl<'d, T: Instance> OpAmp<'d, T> { /// directly used as an ADC input. The opamp will be disabled when the /// [`OpAmpOutput`] is dropped. pub fn buffer_ext( - &'d mut self, + &mut self, in_pin: impl Peripheral

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

+ crate::gpio::Pin> + 'd, + out_pin: impl Peripheral

+ crate::gpio::Pin>, gain: OpAmpGain, - ) -> OpAmpOutput<'d, T> { + ) -> OpAmpOutput<'_, T> { into_ref!(in_pin); into_ref!(out_pin); in_pin.set_as_analog(); @@ -119,9 +120,9 @@ impl<'d, T: Instance> OpAmp<'d, T> { /// [`OpAmpOutput`] is dropped. #[cfg(opamp_g4)] pub fn buffer_dac( - &'d mut self, - out_pin: impl Peripheral

+ crate::gpio::Pin> + 'd, - ) -> OpAmpOutput<'d, T> { + &mut self, + out_pin: impl Peripheral

+ crate::gpio::Pin>, + ) -> OpAmpOutput<'_, T> { into_ref!(out_pin); out_pin.set_as_analog(); @@ -147,10 +148,10 @@ impl<'d, T: Instance> OpAmp<'d, T> { /// The opamp output will be disabled when it is dropped. #[cfg(opamp_g4)] pub fn buffer_int( - &'d mut self, + &mut self, pin: impl Peripheral

+ crate::gpio::Pin>, gain: OpAmpGain, - ) -> OpAmpInternalOutput<'d, T> { + ) -> OpAmpInternalOutput<'_, T> { into_ref!(pin); pin.set_as_analog(); @@ -184,6 +185,7 @@ impl<'d, T: Instance> Drop for OpAmpOutput<'d, T> { } } +#[cfg(opamp_g4)] impl<'d, T: Instance> Drop for OpAmpInternalOutput<'d, T> { fn drop(&mut self) { T::regs().csr().modify(|w| { diff --git a/embassy-stm32/src/ospi/mod.rs b/embassy-stm32/src/ospi/mod.rs index f6eb0d17c..289bfa672 100644 --- a/embassy-stm32/src/ospi/mod.rs +++ b/embassy-stm32/src/ospi/mod.rs @@ -1060,10 +1060,6 @@ pub(crate) trait SealedInstance { const REGS: Regs; } -trait SealedWord { - const CONFIG: u8; -} - /// OSPI instance trait. #[allow(private_bounds)] pub trait Instance: Peripheral

+ SealedInstance + RccPeripheral {} @@ -1110,17 +1106,14 @@ impl<'d, T: Instance, M: PeriMode> GetConfig for Ospi<'d, T, M> { /// Word sizes usable for OSPI. #[allow(private_bounds)] -pub trait Word: word::Word + SealedWord {} +pub trait Word: word::Word {} macro_rules! impl_word { - ($T:ty, $config:expr) => { - impl SealedWord for $T { - const CONFIG: u8 = $config; - } + ($T:ty) => { impl Word for $T {} }; } -impl_word!(u8, 8); -impl_word!(u16, 16); -impl_word!(u32, 32); +impl_word!(u8); +impl_word!(u16); +impl_word!(u32); diff --git a/embassy-stm32/src/rcc/bd.rs b/embassy-stm32/src/rcc/bd.rs index 4e9c18594..9ccca8a2a 100644 --- a/embassy-stm32/src/rcc/bd.rs +++ b/embassy-stm32/src/rcc/bd.rs @@ -16,6 +16,7 @@ pub enum LseMode { Bypass, } +#[derive(Clone, Copy)] pub struct LseConfig { pub frequency: Hertz, pub mode: LseMode, @@ -80,6 +81,7 @@ fn bdcr() -> Reg { return crate::pac::RCC.csr1(); } +#[derive(Clone, Copy)] pub struct LsConfig { pub rtc: RtcClockSource, pub lsi: bool, diff --git a/embassy-stm32/src/rcc/c0.rs b/embassy-stm32/src/rcc/c0.rs index 5adf37941..6712aedc4 100644 --- a/embassy-stm32/src/rcc/c0.rs +++ b/embassy-stm32/src/rcc/c0.rs @@ -37,6 +37,7 @@ pub struct Hsi { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { /// HSI Configuration pub hsi: Option, diff --git a/embassy-stm32/src/rcc/f013.rs b/embassy-stm32/src/rcc/f013.rs index 63dc27bdd..60577b213 100644 --- a/embassy-stm32/src/rcc/f013.rs +++ b/embassy-stm32/src/rcc/f013.rs @@ -76,6 +76,7 @@ pub enum HrtimClockSource { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { pub hsi: bool, pub hse: Option, diff --git a/embassy-stm32/src/rcc/f247.rs b/embassy-stm32/src/rcc/f247.rs index 61f687d30..58056301a 100644 --- a/embassy-stm32/src/rcc/f247.rs +++ b/embassy-stm32/src/rcc/f247.rs @@ -63,6 +63,7 @@ pub struct Pll { /// Used to calculate flash waitstates. See /// RM0033 - Table 3. Number of wait states according to Cortex®-M3 clock frequency #[cfg(stm32f2)] +#[derive(Clone, Copy)] pub enum VoltageScale { /// 2.7 to 3.6 V Range0, @@ -76,6 +77,7 @@ pub enum VoltageScale { /// Configuration of the core clocks #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { pub hsi: bool, pub hse: Option, diff --git a/embassy-stm32/src/rcc/g0.rs b/embassy-stm32/src/rcc/g0.rs index c2fa0ca39..c53c83b0e 100644 --- a/embassy-stm32/src/rcc/g0.rs +++ b/embassy-stm32/src/rcc/g0.rs @@ -33,6 +33,7 @@ pub struct Hse { /// Use this struct to configure the PLL source, input frequency, multiplication factor, and output /// dividers. Be sure to keep check the datasheet for your specific part for the appropriate /// frequency ranges for each of these settings. +#[derive(Clone, Copy)] pub struct Pll { /// PLL Source clock selection. pub source: PllSource, @@ -55,6 +56,7 @@ pub struct Pll { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { /// HSI Enable pub hsi: bool, diff --git a/embassy-stm32/src/rcc/g4.rs b/embassy-stm32/src/rcc/g4.rs index c261c0fed..16561f908 100644 --- a/embassy-stm32/src/rcc/g4.rs +++ b/embassy-stm32/src/rcc/g4.rs @@ -32,6 +32,7 @@ pub struct Hse { /// Use this struct to configure the PLL source, input frequency, multiplication factor, and output /// dividers. Be sure to keep check the datasheet for your specific part for the appropriate /// frequency ranges for each of these settings. +#[derive(Clone, Copy)] pub struct Pll { /// PLL Source clock selection. pub source: PllSource, @@ -54,6 +55,7 @@ pub struct Pll { /// Clocks configutation #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { /// HSI Enable pub hsi: bool, diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index e3c7dd158..376a0b454 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -120,7 +120,7 @@ impl From for Timpre { /// Power supply configuration /// See RM0433 Rev 4 7.4 #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468, pwr_h7rs))] -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq)] pub enum SupplyConfig { /// Default power supply configuration. /// V CORE Power Domains are supplied from the LDO according to VOS. @@ -180,6 +180,7 @@ pub enum SMPSSupplyVoltage { /// Configuration of the core clocks #[non_exhaustive] +#[derive(Clone, Copy)] pub struct Config { pub hsi: Option, pub hse: Option, diff --git a/embassy-stm32/src/rcc/l.rs b/embassy-stm32/src/rcc/l.rs index e9266c65b..6120d33be 100644 --- a/embassy-stm32/src/rcc/l.rs +++ b/embassy-stm32/src/rcc/l.rs @@ -30,6 +30,7 @@ pub struct Hse { } /// Clocks configuration +#[derive(Clone, Copy)] pub struct Config { // base clock sources pub msi: Option, diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 024c63cf5..8022a35a4 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -48,11 +48,22 @@ pub(crate) static mut REFCOUNT_STOP1: u32 = 0; /// May be read without a critical section pub(crate) static mut REFCOUNT_STOP2: u32 = 0; +#[cfg(not(feature = "_dual-core"))] /// Frozen clock frequencies /// /// The existence of this value indicates that the clock configuration can no longer be changed static mut CLOCK_FREQS: MaybeUninit = MaybeUninit::uninit(); +#[cfg(feature = "_dual-core")] +static CLOCK_FREQS_PTR: core::sync::atomic::AtomicPtr> = + core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()); + +#[cfg(feature = "_dual-core")] +pub(crate) fn set_freqs_ptr(freqs: *mut MaybeUninit) { + CLOCK_FREQS_PTR.store(freqs, core::sync::atomic::Ordering::SeqCst); +} + +#[cfg(not(feature = "_dual-core"))] /// Sets the clock frequencies /// /// Safety: Sets a mutable global. @@ -61,11 +72,29 @@ pub(crate) unsafe fn set_freqs(freqs: Clocks) { CLOCK_FREQS = MaybeUninit::new(freqs); } +#[cfg(feature = "_dual-core")] +/// Sets the clock frequencies +/// +/// Safety: Sets a mutable global. +pub(crate) unsafe fn set_freqs(freqs: Clocks) { + debug!("rcc: {:?}", freqs); + CLOCK_FREQS_PTR + .load(core::sync::atomic::Ordering::SeqCst) + .write(MaybeUninit::new(freqs)); +} + +#[cfg(not(feature = "_dual-core"))] /// Safety: Reads a mutable global. pub(crate) unsafe fn get_freqs() -> &'static Clocks { CLOCK_FREQS.assume_init_ref() } +#[cfg(feature = "_dual-core")] +/// Safety: Reads a mutable global. +pub(crate) unsafe fn get_freqs() -> &'static Clocks { + unwrap!(CLOCK_FREQS_PTR.load(core::sync::atomic::Ordering::SeqCst).as_ref()).assume_init_ref() +} + pub(crate) trait SealedRccPeripheral { fn frequency() -> Hertz; const RCC_INFO: RccInfo; diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index d6331f512..28545ca51 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -59,6 +59,7 @@ pub struct Pll { pub divr: Option, } +#[derive(Clone, Copy)] pub struct Config { // base clock sources pub msi: Option, diff --git a/embassy-stm32/src/rcc/wba.rs b/embassy-stm32/src/rcc/wba.rs index 8e1779d7c..1fee648d4 100644 --- a/embassy-stm32/src/rcc/wba.rs +++ b/embassy-stm32/src/rcc/wba.rs @@ -15,6 +15,7 @@ pub struct Hse { } /// Clocks configuration +#[derive(Clone, Copy)] pub struct Config { // base clock sources pub hsi: bool, diff --git a/embassy-stm32/src/rtc/low_power.rs b/embassy-stm32/src/rtc/low_power.rs index a4ff1acbc..875eaa639 100644 --- a/embassy-stm32/src/rtc/low_power.rs +++ b/embassy-stm32/src/rtc/low_power.rs @@ -65,7 +65,7 @@ pub(crate) enum WakeupPrescaler { Div16 = 16, } -#[cfg(any(stm32f4, stm32l0, stm32g4, stm32l5, stm32wb, stm32h5, stm32g0))] +#[cfg(any(stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0))] impl From for crate::pac::rtc::vals::Wucksel { fn from(val: WakeupPrescaler) -> Self { use crate::pac::rtc::vals::Wucksel; @@ -79,7 +79,7 @@ impl From for crate::pac::rtc::vals::Wucksel { } } -#[cfg(any(stm32f4, stm32l0, stm32g4, stm32l5, stm32wb, stm32h5, stm32g0))] +#[cfg(any(stm32f4, stm32l0, stm32g4, stm32l4, stm32l5, stm32wb, stm32h5, stm32g0))] impl From for WakeupPrescaler { fn from(val: crate::pac::rtc::vals::Wucksel) -> Self { use crate::pac::rtc::vals::Wucksel; @@ -132,7 +132,7 @@ impl Rtc { // Panic if the rcc mod knows we're not using low-power rtc #[cfg(any(rcc_wb, rcc_f4, rcc_f410))] - unsafe { crate::rcc::get_freqs() }.rtc.unwrap(); + unsafe { crate::rcc::get_freqs() }.rtc.to_hertz().unwrap(); let requested_duration = requested_duration.as_ticks().clamp(0, u32::MAX as u64); let rtc_hz = Self::frequency().0 as u64; diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs index a7f70b153..fe57cfe66 100644 --- a/embassy-stm32/src/rtc/mod.rs +++ b/embassy-stm32/src/rtc/mod.rs @@ -168,7 +168,7 @@ impl Rtc { fn frequency() -> Hertz { let freqs = unsafe { crate::rcc::get_freqs() }; - freqs.rtc.unwrap() + freqs.rtc.to_hertz().unwrap() } /// Acquire a [`RtcTimeProvider`] instance. diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs index cdc1cb299..5d9025bbe 100644 --- a/embassy-stm32/src/rtc/v2.rs +++ b/embassy-stm32/src/rtc/v2.rs @@ -131,10 +131,13 @@ impl SealedInstance for crate::peripherals::RTC { #[cfg(all(feature = "low-power", stm32f4))] const EXTI_WAKEUP_LINE: usize = 22; + #[cfg(all(feature = "low-power", stm32l4))] + const EXTI_WAKEUP_LINE: usize = 20; + #[cfg(all(feature = "low-power", stm32l0))] const EXTI_WAKEUP_LINE: usize = 20; - #[cfg(all(feature = "low-power", stm32f4))] + #[cfg(all(feature = "low-power", any(stm32f4, stm32l4)))] type WakeupInterrupt = crate::interrupt::typelevel::RTC_WKUP; #[cfg(all(feature = "low-power", stm32l0))] diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index c48d81b5f..63f48ace0 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -661,12 +661,12 @@ fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AfType, AfType) { //sd is defined by tx/rx mode match tx_rx { TxRx::Transmitter => AfType::output(OutputType::PushPull, Speed::VeryHigh), - TxRx::Receiver => AfType::input(Pull::None), + TxRx::Receiver => AfType::input(Pull::Down), // Ensure mute level when no input is connected. }, //clocks (mclk, sck and fs) are defined by master/slave match mode { Mode::Master => AfType::output(OutputType::PushPull, Speed::VeryHigh), - Mode::Slave => AfType::input(Pull::None), + Mode::Slave => AfType::input(Pull::Down), // Ensure no clocks when no input is connected. }, ) } @@ -987,6 +987,21 @@ impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { ch.cr2().modify(|w| w.set_mute(value)); } + /// Determine the mute state of the receiver. + /// + /// Clears the mute state flag in the status register. + pub fn is_muted(&self) -> Result { + match &self.ring_buffer { + RingBuffer::Readable(_) => { + let ch = T::REGS.ch(self.sub_block as usize); + let mute_state = ch.sr().read().mutedet(); + ch.clrfr().write(|w| w.set_cmutedet(true)); + Ok(mute_state) + } + _ => Err(Error::NotAReceiver), + } + } + /// Write data to the SAI ringbuffer. /// /// This appends the data to the buffer and returns immediately. The diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index ee5539518..ed344c412 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -94,6 +94,34 @@ impl DerefMut for DataBlock { } } +/// Command Block buffer for SDMMC command transfers. +/// +/// This is a 16-word array, exposed so that DMA commpatible memory can be used if required. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CmdBlock(pub [u32; 16]); + +impl CmdBlock { + /// Creates a new instance of CmdBlock + pub const fn new() -> Self { + Self([0u32; 16]) + } +} + +impl Deref for CmdBlock { + type Target = [u32; 16]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for CmdBlock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + /// Errors #[non_exhaustive] #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -292,6 +320,10 @@ pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma = NoDma> { signalling: Signalling, /// Card card: Option, + + /// An optional buffer to be used for commands + /// This should be used if there are special memory location requirements for dma + cmd_block: Option<&'d mut CmdBlock>, } const CLK_AF: AfType = AfType::output(OutputType::PushPull, Speed::VeryHigh); @@ -495,6 +527,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { clock: SD_INIT_FREQ, signalling: Default::default(), card: None, + cmd_block: None, } } @@ -531,8 +564,10 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { /// # Safety /// /// `buffer` must be valid for the whole transfer and word aligned + #[allow(unused_variables)] fn prepare_datapath_read<'a>( - &'a mut self, + config: &Config, + dma: &'a mut PeripheralRef<'d, Dma>, buffer: &'a mut [u32], length_bytes: u32, block_size: u8, @@ -544,15 +579,14 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Self::wait_idle(); Self::clear_interrupt_flags(); - regs.dtimer() - .write(|w| w.set_datatime(self.config.data_transfer_timeout)); + regs.dtimer().write(|w| w.set_datatime(config.data_transfer_timeout)); regs.dlenr().write(|w| w.set_datalength(length_bytes)); #[cfg(sdmmc_v1)] let transfer = unsafe { - let request = self.dma.request(); + let request = dma.request(); Transfer::new_read( - &mut self.dma, + dma, request, regs.fifor().as_ptr() as *mut u32, buffer, @@ -692,13 +726,16 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Signalling::SDR12 => 0xFF_FF00, }; - let mut status = [0u32; 16]; + let status = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; // Arm `OnDrop` after the buffer, so it will be dropped first let regs = T::regs(); let on_drop = OnDrop::new(|| Self::on_drop()); - let transfer = self.prepare_datapath_read(&mut status, 64, 6); + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, status.as_mut(), 64, 6); InterruptHandler::::data_interrupts(true); Self::cmd(Cmd::cmd6(set_function), true)?; // CMD6 @@ -770,16 +807,21 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { let card = self.card.as_mut().ok_or(Error::NoCard)?; let rca = card.rca; + let cmd_block = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + Self::cmd(Cmd::set_block_length(64), false)?; // CMD16 Self::cmd(Cmd::app_cmd(rca << 16), false)?; // APP - let mut status = [0u32; 16]; + let status = cmd_block; // Arm `OnDrop` after the buffer, so it will be dropped first let regs = T::regs(); let on_drop = OnDrop::new(|| Self::on_drop()); - let transfer = self.prepare_datapath_read(&mut status, 64, 6); + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, status.as_mut(), 64, 6); InterruptHandler::::data_interrupts(true); Self::cmd(Cmd::card_status(0), true)?; @@ -813,7 +855,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { for byte in status.iter_mut() { *byte = u32::from_be(*byte); } - self.card.as_mut().unwrap().status = status.into(); + self.card.as_mut().unwrap().status = status.0.into(); } res } @@ -872,13 +914,17 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Self::cmd(Cmd::set_block_length(8), false)?; // CMD16 Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; - let mut scr = [0u32; 2]; + let cmd_block = match self.cmd_block.as_deref_mut() { + Some(x) => x, + None => &mut CmdBlock::new(), + }; + let scr = &mut cmd_block.0[..2]; // Arm `OnDrop` after the buffer, so it will be dropped first let regs = T::regs(); let on_drop = OnDrop::new(|| Self::on_drop()); - let transfer = self.prepare_datapath_read(&mut scr[..], 8, 3); + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, scr, 8, 3); InterruptHandler::::data_interrupts(true); Self::cmd(Cmd::cmd51(), true)?; @@ -910,7 +956,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { drop(transfer); unsafe { - let scr_bytes = &*(&scr as *const [u32; 2] as *const [u8; 8]); + let scr_bytes = &*(&scr as *const _ as *const [u8; 8]); card.scr = SCR(u64::from_be_bytes(*scr_bytes)); } } @@ -1002,8 +1048,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { Self::stop_datapath(); } - /// Initializes card (if present) and sets the bus at the - /// specified frequency. + /// Initializes card (if present) and sets the bus at the specified frequency. pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { let regs = T::regs(); let ker_ck = T::frequency(); @@ -1143,6 +1188,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { } } } + // Read status after signalling change self.read_sd_status().await?; @@ -1168,7 +1214,7 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { let regs = T::regs(); let on_drop = OnDrop::new(|| Self::on_drop()); - let transfer = self.prepare_datapath_read(buffer, 512, 9); + let transfer = Self::prepare_datapath_read(&self.config, &mut self.dma, buffer, 512, 9); InterruptHandler::::data_interrupts(true); Self::cmd(Cmd::read_single_block(address), true)?; @@ -1291,6 +1337,14 @@ impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { pub fn clock(&self) -> Hertz { self.clock } + + /// Set a specific cmd buffer rather than using the default stack allocated one. + /// This is required if stack RAM cannot be used with DMA and usually manifests + /// itself as an indefinite wait on a dma transfer because the dma peripheral + /// cannot access the memory. + pub fn set_cmd_block(&mut self, cmd_block: &'d mut CmdBlock) { + self.cmd_block = Some(cmd_block) + } } impl<'d, T: Instance, Dma: SdmmcDma + 'd> Drop for Sdmmc<'d, T, Dma> { @@ -1457,3 +1511,44 @@ foreach_peripheral!( } }; ); + +impl<'d, T: Instance, Dma: SdmmcDma + 'd> block_device_driver::BlockDevice<512> for Sdmmc<'d, T, Dma> { + type Error = Error; + type Align = aligned::A4; + + async fn read( + &mut self, + mut block_address: u32, + buf: &mut [aligned::Aligned], + ) -> Result<(), Self::Error> { + // FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time + for block in buf.iter_mut() { + // safety aligned by block device + let block = unsafe { &mut *(block as *mut _ as *mut crate::sdmmc::DataBlock) }; + self.read_block(block_address, block).await?; + block_address += 1; + } + + Ok(()) + } + + async fn write( + &mut self, + mut block_address: u32, + buf: &[aligned::Aligned], + ) -> Result<(), Self::Error> { + // FIXME/TODO because of missing read_blocks multiple we have to do this one block at a time + for block in buf.iter() { + // safety aligned by block device + let block = unsafe { &*(block as *const _ as *const crate::sdmmc::DataBlock) }; + self.write_block(block_address, block).await?; + block_address += 1; + } + + Ok(()) + } + + async fn size(&mut self) -> Result { + Ok(self.card()?.size()) + } +} diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 656676d9f..20718147a 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -1282,6 +1282,7 @@ pub(crate) struct Info { struct State {} impl State { + #[allow(unused)] const fn new() -> Self { Self {} } diff --git a/embassy-stm32/src/time.rs b/embassy-stm32/src/time.rs index 17690aefc..802ff41ce 100644 --- a/embassy-stm32/src/time.rs +++ b/embassy-stm32/src/time.rs @@ -87,3 +87,39 @@ impl Div for Hertz { self.0 / rhs.0 } } + +#[repr(C)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// A variant on [Hertz] that acts as an `Option` that is smaller and repr C. +/// +/// An `Option` can be `.into()`'d into this type and back. +/// The only restriction is that that [Hertz] cannot have the value 0 since that's +/// seen as the `None` variant. +pub struct MaybeHertz(u32); + +impl MaybeHertz { + /// Same as calling the `.into()` function, but without type inference. + pub fn to_hertz(self) -> Option { + self.into() + } +} + +impl From> for MaybeHertz { + fn from(value: Option) -> Self { + match value { + Some(Hertz(0)) => panic!("Hertz cannot be 0"), + Some(Hertz(val)) => Self(val), + None => Self(0), + } + } +} + +impl From for Option { + fn from(value: MaybeHertz) -> Self { + match value { + MaybeHertz(0) => None, + MaybeHertz(val) => Some(Hertz(val)), + } + } +} diff --git a/embassy-stm32/src/ucpd.rs b/embassy-stm32/src/ucpd.rs index 40aea75cb..ee0a2c7c1 100644 --- a/embassy-stm32/src/ucpd.rs +++ b/embassy-stm32/src/ucpd.rs @@ -27,7 +27,7 @@ use crate::dma::{ChannelAndRequest, TransferOptions}; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; use crate::pac::ucpd::vals::{Anamode, Ccenable, PscUsbpdclk, Txmode}; -pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, TypecVstateCc as CcVState}; +pub use crate::pac::ucpd::vals::{Phyccsel as CcSel, Rxordset, TypecVstateCc as CcVState}; use crate::rcc::{self, RccPeripheral}; pub(crate) fn init( @@ -86,6 +86,34 @@ pub enum CcPull { Source3_0A, } +/// UCPD configuration +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Receive SOP packets + pub sop: bool, + /// Receive SOP' packets + pub sop_prime: bool, + /// Receive SOP'' packets + pub sop_double_prime: bool, + /// Receive SOP'_Debug packets + pub sop_prime_debug: bool, + /// Receive SOP''_Debug packets + pub sop_double_prime_debug: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + sop: true, + sop_prime: false, + sop_double_prime: false, + sop_prime_debug: false, + sop_double_prime_debug: false, + } + } +} + /// UCPD driver. pub struct Ucpd<'d, T: Instance> { cc_phy: CcPhy<'d, T>, @@ -98,6 +126,7 @@ impl<'d, T: Instance> Ucpd<'d, T> { _irq: impl interrupt::typelevel::Binding> + 'd, cc1: impl Peripheral

> + 'd, cc2: impl Peripheral

> + 'd, + config: Config, ) -> Self { into_ref!(cc1, cc2); cc1.set_as_analog(); @@ -129,9 +158,15 @@ impl<'d, T: Instance> Ucpd<'d, T> { // 1.75us * 17 = ~30us w.set_ifrgap(17 - 1); - // TODO: Currently only hard reset and SOP messages can be received. // UNDOCUMENTED: This register can only be written while UCPDEN=0 (found by testing). - w.set_rxordseten(0b1001); + let rxordset = (config.sop as u16) << 0 + | (config.sop_prime as u16) << 1 + | (config.sop_double_prime as u16) << 2 + // Hard reset + | 0x1 << 3 + | (config.sop_prime_debug as u16) << 4 + | (config.sop_double_prime_debug as u16) << 5; + w.set_rxordseten(rxordset); // Enable DMA w.set_txdmaen(true); @@ -288,6 +323,22 @@ impl<'d, T: Instance> CcPhy<'d, T> { } } +/// Receive SOP. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Sop { + /// SOP + Sop, + /// SOP' + SopPrime, + /// SOP'' + SopDoublePrime, + /// SOP'_Debug + SopPrimeDebug, + /// SOP''_Debug + SopDoublePrimeDebug, +} + /// Receive Error. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -340,6 +391,13 @@ impl<'d, T: Instance> PdPhy<'d, T> { /// /// Returns the number of received bytes or an error. pub async fn receive(&mut self, buf: &mut [u8]) -> Result { + self.receive_with_sop(buf).await.map(|(_sop, size)| size) + } + + /// Receives SOP and a PD message into the provided buffer. + /// + /// Returns the start of packet type and number of received bytes or an error. + pub async fn receive_with_sop(&mut self, buf: &mut [u8]) -> Result<(Sop, usize), RxError> { let r = T::REGS; let dma = unsafe { @@ -388,7 +446,18 @@ impl<'d, T: Instance> PdPhy<'d, T> { } } - Ok(r.rx_payszr().read().rxpaysz().into()) + let sop = match r.rx_ordsetr().read().rxordset() { + Rxordset::SOP => Sop::Sop, + Rxordset::SOPPRIME => Sop::SopPrime, + Rxordset::SOPDOUBLEPRIME => Sop::SopDoublePrime, + Rxordset::SOPPRIMEDEBUG => Sop::SopPrimeDebug, + Rxordset::SOPDOUBLEPRIMEDEBUG => Sop::SopDoublePrimeDebug, + Rxordset::CABLERESET => return Err(RxError::HardReset), + // Extension headers are not supported + _ => unreachable!(), + }; + + Ok((sop, r.rx_payszr().read().rxpaysz().into())) } fn enable_rx_interrupt(enable: bool) { diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 06cc0e41d..86f56eb7c 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -12,8 +12,8 @@ use embassy_sync::waitqueue::AtomicWaker; #[cfg(not(any(usart_v1, usart_v2)))] use super::DePin; use super::{ - clear_interrupt_flags, configure, rdr, reconfigure, sr, tdr, Config, ConfigError, CtsPin, Error, Info, Instance, - Regs, RtsPin, RxPin, TxPin, + clear_interrupt_flags, configure, rdr, reconfigure, send_break, sr, tdr, Config, ConfigError, CtsPin, Error, Info, + Instance, Regs, RtsPin, RxPin, TxPin, }; use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::interrupt::{self, InterruptExt}; @@ -359,6 +359,11 @@ impl<'d> BufferedUart<'d> { Ok(()) } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break() + } } impl<'d> BufferedUartRx<'d> { @@ -538,6 +543,11 @@ impl<'d> BufferedUartTx<'d> { Ok(()) } + + /// Send break character + pub fn send_break(&self) { + send_break(&self.info.regs); + } } impl<'d> Drop for BufferedUartRx<'d> { diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 7ed3793a1..e7f2f890a 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -14,7 +14,7 @@ use embassy_sync::waitqueue::AtomicWaker; use futures_util::future::{select, Either}; use crate::dma::ChannelAndRequest; -use crate::gpio::{AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; +use crate::gpio::{self, AfType, AnyPin, OutputType, Pull, SealedPin as _, Speed}; use crate::interrupt::typelevel::Interrupt as _; use crate::interrupt::{self, Interrupt, InterruptExt}; use crate::mode::{Async, Blocking, Mode}; @@ -211,6 +211,30 @@ impl Default for Config { } } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Half duplex IO mode +pub enum HalfDuplexConfig { + /// Push pull allows for faster baudrates, may require series resistor + PushPull, + /// Open drain output using external pull up resistor + OpenDrainExternal, + #[cfg(not(gpio_v1))] + /// Open drain output using internal pull up resistor + OpenDrainInternal, +} + +impl HalfDuplexConfig { + fn af_type(self) -> gpio::AfType { + match self { + HalfDuplexConfig::PushPull => AfType::output(OutputType::PushPull, Speed::Medium), + HalfDuplexConfig::OpenDrainExternal => AfType::output(OutputType::OpenDrain, Speed::Medium), + #[cfg(not(gpio_v1))] + HalfDuplexConfig::OpenDrainInternal => AfType::output_pull(OutputType::OpenDrain, Speed::Medium, Pull::Up), + } + } +} + /// Serial error #[derive(Debug, Eq, PartialEq, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -496,6 +520,11 @@ impl<'d, M: Mode> UartTx<'d, M> { pub fn blocking_flush(&mut self) -> Result<(), Error> { blocking_flush(self.info) } + + /// Send break character + pub fn send_break(&self) { + send_break(&self.info.regs); + } } fn blocking_flush(info: &Info) -> Result<(), Error> { @@ -510,6 +539,21 @@ fn blocking_flush(info: &Info) -> Result<(), Error> { Ok(()) } +/// Send break character +pub fn send_break(regs: &Regs) { + // Busy wait until previous break has been sent + #[cfg(any(usart_v1, usart_v2))] + while regs.cr1().read().sbk() {} + #[cfg(any(usart_v3, usart_v4))] + while regs.isr().read().sbkf() {} + + // Send break right after completing the current character transmission + #[cfg(any(usart_v1, usart_v2))] + regs.cr1().modify(|w| w.set_sbk(true)); + #[cfg(any(usart_v3, usart_v4))] + regs.rqr().write(|w| w.set_sbkrq(true)); +} + impl<'d> UartRx<'d, Async> { /// Create a new rx-only UART with no hardware flow control. /// @@ -1029,8 +1073,9 @@ impl<'d> Uart<'d, Async> { /// (when it is available for your chip). There is no functional difference between these methods, as both /// allow bidirectional communication. /// - /// The pin is always released when no data is transmitted. Thus, it acts as a standard - /// I/O in idle or in reception. + /// The TX pin is always released when no data is transmitted. Thus, it acts as a standard + /// I/O in idle or in reception. It means that the I/O must be configured so that TX is + /// configured as alternate function open-drain with an external pull-up /// Apart from this, the communication protocol is similar to normal USART mode. Any conflict /// on the line must be managed by software (for instance by using a centralized arbiter). #[doc(alias("HDSEL"))] @@ -1041,6 +1086,7 @@ impl<'d> Uart<'d, Async> { tx_dma: impl Peripheral

> + 'd, rx_dma: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { #[cfg(not(any(usart_v1, usart_v2)))] { @@ -1051,7 +1097,7 @@ impl<'d> Uart<'d, Async> { Self::new_inner( peri, None, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(tx, half_duplex.af_type()), None, None, None, @@ -1079,6 +1125,7 @@ impl<'d> Uart<'d, Async> { tx_dma: impl Peripheral

> + 'd, rx_dma: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { config.swap_rx_tx = true; config.half_duplex = true; @@ -1087,7 +1134,7 @@ impl<'d> Uart<'d, Async> { peri, None, None, - new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, half_duplex.af_type()), None, None, new_dma!(tx_dma), @@ -1192,6 +1239,7 @@ impl<'d> Uart<'d, Blocking> { peri: impl Peripheral

+ 'd, tx: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { #[cfg(not(any(usart_v1, usart_v2)))] { @@ -1202,7 +1250,7 @@ impl<'d> Uart<'d, Blocking> { Self::new_inner( peri, None, - new_pin!(tx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(tx, half_duplex.af_type()), None, None, None, @@ -1227,6 +1275,7 @@ impl<'d> Uart<'d, Blocking> { peri: impl Peripheral

+ 'd, rx: impl Peripheral

> + 'd, mut config: Config, + half_duplex: HalfDuplexConfig, ) -> Result { config.swap_rx_tx = true; config.half_duplex = true; @@ -1235,7 +1284,7 @@ impl<'d> Uart<'d, Blocking> { peri, None, None, - new_pin!(rx, AfType::output(OutputType::PushPull, Speed::Medium)), + new_pin!(rx, half_duplex.af_type()), None, None, None, @@ -1336,6 +1385,11 @@ impl<'d, M: Mode> Uart<'d, M> { pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { (self.tx, self.rx) } + + /// Send break character + pub fn send_break(&self) { + self.tx.send_break(); + } } fn reconfigure(info: &Info, kernel_clock: Hertz, config: &Config) -> Result<(), ConfigError> { diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 8cf75933a..b0652046c 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -184,20 +184,6 @@ impl<'d> RingBufferedUartRx<'d> { async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> { compiler_fence(Ordering::SeqCst); - let mut dma_init = false; - // Future which completes when there is dma is half full or full - let dma = poll_fn(|cx| { - self.ring_buf.set_waker(cx.waker()); - - let status = match dma_init { - false => Poll::Pending, - true => Poll::Ready(()), - }; - - dma_init = true; - status - }); - // Future which completes when idle line is detected let s = self.state; let uart = poll_fn(|cx| { @@ -219,9 +205,23 @@ impl<'d> RingBufferedUartRx<'d> { } }); - match select(dma, uart).await { - Either::Left(((), _)) => Ok(()), - Either::Right((result, _)) => result, + let mut dma_init = false; + // Future which completes when there is dma is half full or full + let dma = poll_fn(|cx| { + self.ring_buf.set_waker(cx.waker()); + + let status = match dma_init { + false => Poll::Pending, + true => Poll::Ready(()), + }; + + dma_init = true; + status + }); + + match select(uart, dma).await { + Either::Left((result, _)) => result, + Either::Right(((), _)) => Ok(()), } } } diff --git a/embassy-stm32/src/usb/otg.rs b/embassy-stm32/src/usb/otg.rs index 8ee8dcc36..e27b164e4 100644 --- a/embassy-stm32/src/usb/otg.rs +++ b/embassy-stm32/src/usb/otg.rs @@ -97,6 +97,55 @@ impl<'d, T: Instance> Driver<'d, T> { } } + /// Initializes USB OTG peripheral with external Full-speed PHY (usually, a High-speed PHY in Full-speed mode). + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store received packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_fs_ulpi( + _peri: impl Peripheral

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

> + 'd, + ulpi_dir: impl Peripheral

> + 'd, + ulpi_nxt: impl Peripheral

> + 'd, + ulpi_stp: impl Peripheral

> + 'd, + ulpi_d0: impl Peripheral

> + 'd, + ulpi_d1: impl Peripheral

> + 'd, + ulpi_d2: impl Peripheral

> + 'd, + ulpi_d3: impl Peripheral

> + 'd, + ulpi_d4: impl Peripheral

> + 'd, + ulpi_d5: impl Peripheral

> + 'd, + ulpi_d6: impl Peripheral

> + 'd, + ulpi_d7: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + config_ulpi_pins!( + ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, + ulpi_d7 + ); + + let regs = T::regs(); + + let instance = OtgInstance { + regs: T::regs(), + state: T::state(), + fifo_depth_words: T::FIFO_DEPTH_WORDS, + extra_rx_fifo_words: RX_FIFO_EXTRA_SIZE_WORDS, + endpoint_count: T::ENDPOINT_COUNT, + phy_type: PhyType::ExternalFullSpeed, + quirk_setup_late_cnak: quirk_setup_late_cnak(regs), + calculate_trdt_fn: calculate_trdt::, + }; + + Self { + inner: OtgDriver::new(ep_out_buffer, instance, config), + phantom: PhantomData, + } + } + /// Initializes USB OTG peripheral with external High-Speed PHY. /// /// # Arguments diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index 1d9d19a73..0ab2306c8 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -80,6 +80,8 @@ impl interrupt::typelevel::Handler for InterruptHandl if istr.ctr() { let index = istr.ep_id() as usize; + CTR_TRIGGERED[index].store(true, Ordering::Relaxed); + let mut epr = regs.epr(index).read(); if epr.ctr_rx() { if index == 0 && epr.setup() { @@ -120,6 +122,10 @@ const USBRAM_ALIGN: usize = 4; const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; static EP0_SETUP: AtomicBool = AtomicBool::new(false); + +const NEW_CTR_TRIGGERED: AtomicBool = AtomicBool::new(false); +static CTR_TRIGGERED: [AtomicBool; EP_COUNT] = [NEW_CTR_TRIGGERED; EP_COUNT]; + static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; static IRQ_RESET: AtomicBool = AtomicBool::new(false); @@ -163,20 +169,37 @@ fn calc_out_len(len: u16) -> (u16, u16) { mod btable { use super::*; - pub(super) fn write_in(index: usize, addr: u16) { + pub(super) fn write_in_tx(index: usize, addr: u16) { USBRAM.mem(index * 4 + 0).write_value(addr); } - pub(super) fn write_in_len(index: usize, _addr: u16, len: u16) { + pub(super) fn write_in_rx(index: usize, addr: u16) { + USBRAM.mem(index * 4 + 2).write_value(addr); + } + + pub(super) fn write_in_len_rx(index: usize, _addr: u16, len: u16) { + USBRAM.mem(index * 4 + 3).write_value(len); + } + + pub(super) fn write_in_len_tx(index: usize, _addr: u16, len: u16) { USBRAM.mem(index * 4 + 1).write_value(len); } - pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { USBRAM.mem(index * 4 + 2).write_value(addr); USBRAM.mem(index * 4 + 3).write_value(max_len_bits); } - pub(super) fn read_out_len(index: usize) -> u16 { + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM.mem(index * 4 + 0).write_value(addr); + USBRAM.mem(index * 4 + 1).write_value(max_len_bits); + } + + pub(super) fn read_out_len_tx(index: usize) -> u16 { + USBRAM.mem(index * 4 + 1).read() + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { USBRAM.mem(index * 4 + 3).read() } } @@ -184,19 +207,37 @@ mod btable { mod btable { use super::*; - pub(super) fn write_in(_index: usize, _addr: u16) {} + pub(super) fn write_in_tx(_index: usize, _addr: u16) {} - pub(super) fn write_in_len(index: usize, addr: u16, len: u16) { + pub(super) fn write_in_rx(_index: usize, _addr: u16) {} + + pub(super) fn write_in_len_tx(index: usize, addr: u16, len: u16) { USBRAM.mem(index * 2).write_value((addr as u32) | ((len as u32) << 16)); } - pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + pub(super) fn write_in_len_rx(index: usize, addr: u16, len: u16) { + USBRAM + .mem(index * 2 + 1) + .write_value((addr as u32) | ((len as u32) << 16)); + } + + pub(super) fn write_out_tx(index: usize, addr: u16, max_len_bits: u16) { + USBRAM + .mem(index * 2) + .write_value((addr as u32) | ((max_len_bits as u32) << 16)); + } + + pub(super) fn write_out_rx(index: usize, addr: u16, max_len_bits: u16) { USBRAM .mem(index * 2 + 1) .write_value((addr as u32) | ((max_len_bits as u32) << 16)); } - pub(super) fn read_out_len(index: usize) -> u16 { + pub(super) fn read_out_len_tx(index: usize) -> u16 { + (USBRAM.mem(index * 2).read() >> 16) as u16 + } + + pub(super) fn read_out_len_rx(index: usize) -> u16 { (USBRAM.mem(index * 2 + 1).read() >> 16) as u16 } } @@ -270,7 +311,7 @@ impl<'d, T: Instance> Driver<'d, T> { #[cfg(feature = "time")] embassy_time::block_for(embassy_time::Duration::from_millis(100)); #[cfg(not(feature = "time"))] - cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.unwrap().0 / 10); + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 10); #[cfg(not(usb_v4))] regs.btable().write(|w| w.set_btable(0)); @@ -327,6 +368,13 @@ impl<'d, T: Instance> Driver<'d, T> { return false; // reserved for control pipe } let used = ep.used_out || ep.used_in; + if used && (ep.ep_type == EndpointType::Isochronous || ep.ep_type == EndpointType::Bulk) { + // Isochronous and bulk endpoints are double-buffered. + // Their corresponding endpoint/channel registers are forced to be unidirectional. + // Do not reuse this index. + return false; + } + let used_dir = match D::dir() { Direction::Out => ep.used_out, Direction::In => ep.used_in, @@ -350,7 +398,11 @@ impl<'d, T: Instance> Driver<'d, T> { let addr = self.alloc_ep_mem(len); trace!(" len_bits = {:04x}", len_bits); - btable::write_out::(index, addr, len_bits); + btable::write_out_rx::(index, addr, len_bits); + + if ep_type == EndpointType::Isochronous { + btable::write_out_tx::(index, addr, len_bits); + } EndpointBuffer { addr, @@ -366,7 +418,11 @@ impl<'d, T: Instance> Driver<'d, T> { let addr = self.alloc_ep_mem(len); // ep_in_len is written when actually TXing packets. - btable::write_in::(index, addr); + btable::write_in_tx::(index, addr); + + if ep_type == EndpointType::Isochronous { + btable::write_in_rx::(index, addr); + } EndpointBuffer { addr, @@ -656,6 +712,18 @@ impl Dir for Out { } } +/// Selects the packet buffer. +/// +/// For double-buffered endpoints, both the `Rx` and `Tx` buffer from a channel are used for the same +/// direction of transfer. This is opposed to single-buffered endpoints, where one channel can serve +/// two directions at the same time. +enum PacketBuffer { + /// The RX buffer - must be used for single-buffered OUT endpoints + Rx, + /// The TX buffer - must be used for single-buffered IN endpoints + Tx, +} + /// USB endpoint. pub struct Endpoint<'d, T: Instance, D> { _phantom: PhantomData<(&'d mut T, D)>, @@ -664,15 +732,46 @@ pub struct Endpoint<'d, T: Instance, D> { } impl<'d, T: Instance, D> Endpoint<'d, T, D> { - fn write_data(&mut self, buf: &[u8]) { + /// Write to a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to write to the right counter field. + /// The DTOG_TX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next transmit packet will be stored, so we need to write the counter of the OTHER buffer, which is + /// where the last transmitted packet was stored. + fn write_data_double_buffered(&mut self, buf: &[u8], packet_buffer: PacketBuffer) { let index = self.info.addr.index(); self.buf.write(buf); - btable::write_in_len::(index, self.buf.addr, buf.len() as _); + + match packet_buffer { + PacketBuffer::Rx => btable::write_in_len_rx::(index, self.buf.addr, buf.len() as _), + PacketBuffer::Tx => btable::write_in_len_tx::(index, self.buf.addr, buf.len() as _), + } } - fn read_data(&mut self, buf: &mut [u8]) -> Result { + /// Write to a single-buffered endpoint. + fn write_data(&mut self, buf: &[u8]) { + self.write_data_double_buffered(buf, PacketBuffer::Tx); + } + + /// Read from a double-buffered endpoint. + /// + /// For double-buffered endpoints, the data buffers overlap, but we still need to read from the right counter field. + /// The DTOG_RX bit indicates the buffer that is currently in use by the USB peripheral, that is, the buffer in + /// which the next received packet will be stored, so we need to read the counter of the OTHER buffer, which is + /// where the last received packet was stored. + fn read_data_double_buffered( + &mut self, + buf: &mut [u8], + packet_buffer: PacketBuffer, + ) -> Result { let index = self.info.addr.index(); - let rx_len = btable::read_out_len::(index) as usize & 0x3FF; + + let rx_len = match packet_buffer { + PacketBuffer::Rx => btable::read_out_len_rx::(index), + PacketBuffer::Tx => btable::read_out_len_tx::(index), + } as usize + & 0x3FF; + trace!("READ DONE, rx_len = {}", rx_len); if rx_len > buf.len() { return Err(EndpointError::BufferOverflow); @@ -680,6 +779,11 @@ impl<'d, T: Instance, D> Endpoint<'d, T, D> { self.buf.read(&mut buf[..rx_len]); Ok(rx_len) } + + /// Read from a single-buffered endpoint. + fn read_data(&mut self, buf: &mut [u8]) -> Result { + self.read_data_double_buffered(buf, PacketBuffer::Rx) + } } impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { @@ -734,25 +838,53 @@ impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { EP_OUT_WAKERS[index].register(cx.waker()); let regs = T::regs(); let stat = regs.epr(index).read().stat_rx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) { + Poll::Ready(stat) + } else { + Poll::Pending + } } else { - Poll::Pending + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } } }) .await; + CTR_TRIGGERED[index].store(false, Ordering::Relaxed); + if stat == Stat::DISABLED { return Err(EndpointError::Disabled); } - let rx_len = self.read_data(buf)?; - let regs = T::regs(); + + let packet_buffer = if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Read from the OTHER buffer. + if regs.epr(index).read().dtog_rx() { + PacketBuffer::Rx + } else { + PacketBuffer::Tx + } + } else { + PacketBuffer::Rx + }; + + let rx_len = self.read_data_double_buffered(buf, packet_buffer)?; + regs.epr(index).write(|w| { w.set_ep_type(convert_type(self.info.ep_type)); w.set_ea(self.info.addr.index() as _); - w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + if self.info.ep_type == EndpointType::Isochronous { + w.set_stat_rx(Stat::from_bits(0)); // STAT_RX remains `VALID`. + } else { + w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + } w.set_stat_tx(Stat::from_bits(0)); w.set_ctr_rx(true); // don't clear w.set_ctr_tx(true); // don't clear @@ -776,25 +908,54 @@ impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { EP_IN_WAKERS[index].register(cx.waker()); let regs = T::regs(); let stat = regs.epr(index).read().stat_tx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) + if self.info.ep_type == EndpointType::Isochronous { + // The isochronous endpoint does not change its `STAT_RX` field to `NAK` when receiving a packet. + // Therefore, this instead waits until the `CTR` interrupt was triggered. + if matches!(stat, Stat::DISABLED) || CTR_TRIGGERED[index].load(Ordering::Relaxed) { + Poll::Ready(stat) + } else { + Poll::Pending + } } else { - Poll::Pending + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } } }) .await; + CTR_TRIGGERED[index].store(false, Ordering::Relaxed); + if stat == Stat::DISABLED { return Err(EndpointError::Disabled); } - self.write_data(buf); + let regs = T::regs(); + + let packet_buffer = if self.info.ep_type == EndpointType::Isochronous { + // Find the buffer, which is currently in use. Write to the OTHER buffer. + if regs.epr(index).read().dtog_tx() { + PacketBuffer::Tx + } else { + PacketBuffer::Rx + } + } else { + PacketBuffer::Tx + }; + + self.write_data_double_buffered(buf, packet_buffer); let regs = T::regs(); regs.epr(index).write(|w| { w.set_ep_type(convert_type(self.info.ep_type)); w.set_ea(self.info.addr.index() as _); - w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + if self.info.ep_type == EndpointType::Isochronous { + w.set_stat_tx(Stat::from_bits(0)); // STAT_TX remains `VALID`. + } else { + w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + } w.set_stat_rx(Stat::from_bits(0)); w.set_ctr_rx(true); // don't clear w.set_ctr_tx(true); // don't clear diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md index a283adc0c..8f2d26fe0 100644 --- a/embassy-sync/CHANGELOG.md +++ b/embassy-sync/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Add LazyLock sync primitive. + ## 0.6.0 - 2024-05-29 - Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `Channel`. diff --git a/embassy-sync/README.md b/embassy-sync/README.md index 2c1c0cf68..97a663d6d 100644 --- a/embassy-sync/README.md +++ b/embassy-sync/README.md @@ -5,7 +5,7 @@ An [Embassy](https://embassy.dev) project. Synchronization primitives and data structures with async support: - [`Channel`](channel::Channel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. -- [`PriorityChannel`](channel::priority::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. +- [`PriorityChannel`](channel::priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. - [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. - [`Signal`](signal::Signal) - Signalling latest value to a single consumer. - [`Mutex`](mutex::Mutex) - Mutex for synchronizing state between asynchronous tasks. @@ -13,6 +13,7 @@ Synchronization primitives and data structures with async support: - [`WakerRegistration`](waitqueue::WakerRegistration) - Utility to register and wake a `Waker`. - [`AtomicWaker`](waitqueue::AtomicWaker) - A variant of `WakerRegistration` accessible using a non-mut API. - [`MultiWakerRegistration`](waitqueue::MultiWakerRegistration) - Utility registering and waking multiple `Waker`'s. +- [`LazyLock`](lazy_lock::LazyLock) - A value which is initialized on the first access ## Interoperability diff --git a/embassy-sync/build_common.rs b/embassy-sync/build_common.rs index 0487eb3c5..4f24e6d37 100644 --- a/embassy-sync/build_common.rs +++ b/embassy-sync/build_common.rs @@ -8,8 +8,6 @@ use std::collections::HashSet; use std::env; -use std::ffi::OsString; -use std::process::Command; /// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring /// them (`cargo:rust-check-cfg=cfg(X)`). @@ -17,7 +15,6 @@ use std::process::Command; pub struct CfgSet { enabled: HashSet, declared: HashSet, - emit_declared: bool, } impl CfgSet { @@ -25,7 +22,6 @@ impl CfgSet { Self { enabled: HashSet::new(), declared: HashSet::new(), - emit_declared: is_rustc_nightly(), } } @@ -49,7 +45,7 @@ impl CfgSet { /// /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. pub fn declare(&mut self, cfg: impl AsRef) { - if self.declared.insert(cfg.as_ref().to_owned()) && self.emit_declared { + if self.declared.insert(cfg.as_ref().to_owned()) { println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); } } @@ -69,21 +65,6 @@ impl CfgSet { } } -fn is_rustc_nightly() -> bool { - if env::var_os("EMBASSY_FORCE_CHECK_CFG").is_some() { - return true; - } - - let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); - - let output = Command::new(rustc) - .arg("--version") - .output() - .expect("failed to run `rustc --version`"); - - String::from_utf8_lossy(&output.stdout).contains("nightly") -} - /// Sets configs that describe the target platform. pub fn set_target_cfgs(cfgs: &mut CfgSet) { let target = env::var("TARGET").unwrap(); diff --git a/embassy-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs new file mode 100644 index 000000000..18e3c2019 --- /dev/null +++ b/embassy-sync/src/lazy_lock.rs @@ -0,0 +1,152 @@ +//! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. + +use core::cell::UnsafeCell; +use core::mem::ManuallyDrop; +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The `LazyLock` is a synchronization primitive that allows for +/// initializing a value once, and allowing others to obtain a +/// reference to the value. This is useful for lazy initialization of +/// a static value. +/// +/// # Example +/// ``` +/// use futures_executor::block_on; +/// use embassy_sync::lazy_lock::LazyLock; +/// +/// // Define a static value that will be lazily initialized +/// // at runtime at the first access. +/// static VALUE: LazyLock = LazyLock::new(|| 20); +/// +/// let reference = VALUE.get(); +/// assert_eq!(reference, &20); +/// ``` +pub struct LazyLock T> { + init: AtomicBool, + data: UnsafeCell>, +} + +union Data { + value: ManuallyDrop, + f: ManuallyDrop, +} + +unsafe impl Sync for LazyLock {} + +impl T> LazyLock { + /// Create a new uninitialized `StaticLock`. + pub const fn new(init_fn: F) -> Self { + Self { + init: AtomicBool::new(false), + data: UnsafeCell::new(Data { + f: ManuallyDrop::new(init_fn), + }), + } + } + + /// Get a reference to the underlying value, initializing it if it + /// has not been done already. + #[inline] + pub fn get(&self) -> &T { + self.ensure_init_fast(); + unsafe { &(*self.data.get()).value } + } + + /// Consume the `LazyLock`, returning the underlying value. The + /// initialization function will be called if it has not been + /// already. + #[inline] + pub fn into_inner(self) -> T { + self.ensure_init_fast(); + let this = ManuallyDrop::new(self); + let data = unsafe { core::ptr::read(&this.data) }.into_inner(); + + ManuallyDrop::into_inner(unsafe { data.value }) + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// This function is a fast track to [`Self::ensure_init`] + /// which does not require a critical section in most cases when + /// the value has been initialized already. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + #[inline] + fn ensure_init_fast(&self) { + if !self.init.load(Ordering::Acquire) { + self.ensure_init(); + } + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + fn ensure_init(&self) { + critical_section::with(|_| { + if !self.init.load(Ordering::Acquire) { + let data = unsafe { &mut *self.data.get() }; + let f = unsafe { ManuallyDrop::take(&mut data.f) }; + let value = f(); + data.value = ManuallyDrop::new(value); + + self.init.store(true, Ordering::Release); + } + }); + } +} + +impl Drop for LazyLock { + fn drop(&mut self) { + if self.init.load(Ordering::Acquire) { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().value) }; + } else { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().f) }; + } + } +} + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicU32, Ordering}; + + use super::*; + + #[test] + fn test_lazy_lock() { + static VALUE: LazyLock = LazyLock::new(|| 20); + let reference = VALUE.get(); + assert_eq!(reference, &20); + } + #[test] + fn test_lazy_lock_into_inner() { + let lazy: LazyLock = LazyLock::new(|| 20); + let value = lazy.into_inner(); + assert_eq!(value, 20); + } + + static DROP_CHECKER: AtomicU32 = AtomicU32::new(0); + struct DropCheck; + + impl Drop for DropCheck { + fn drop(&mut self) { + DROP_CHECKER.fetch_add(1, Ordering::Acquire); + } + } + + #[test] + fn test_lazy_drop() { + let lazy: LazyLock = LazyLock::new(|| DropCheck); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 0); + lazy.get(); + drop(lazy); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + + let dropper = DropCheck; + let lazy_fn: LazyLock = LazyLock::new(move || { + let _a = dropper; + 20 + }); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + drop(lazy_fn); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 2); + } +} diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index a5eee8d02..014bf1d06 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -12,6 +12,7 @@ mod ring_buffer; pub mod blocking_mutex; pub mod channel; +pub mod lazy_lock; pub mod mutex; pub mod once_lock; pub mod pipe; diff --git a/embassy-time-driver/src/lib.rs b/embassy-time-driver/src/lib.rs index 565597935..8000a9dcb 100644 --- a/embassy-time-driver/src/lib.rs +++ b/embassy-time-driver/src/lib.rs @@ -98,7 +98,7 @@ pub trait Driver: Send + Sync + 'static { /// /// Implementations MUST ensure that: /// - This is guaranteed to be monotonic, i.e. a call to now() will always return - /// a greater or equal value than earler calls. Time can't "roll backwards". + /// a greater or equal value than earlier calls. Time can't "roll backwards". /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say /// in 10_000 years (Human civilization is likely to already have self-destructed /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers @@ -113,24 +113,52 @@ pub trait Driver: Send + Sync + 'static { /// It is UB to make the alarm fire before setting a callback. unsafe fn allocate_alarm(&self) -> Option; - /// Sets the callback function to be called when the alarm triggers. + /// Set the callback function to be called when the alarm triggers. /// The callback may be called from any context (interrupt or thread mode). fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm - /// timestamp, the provided callback function will be called. + /// Set an alarm at the given timestamp. /// - /// The `Driver` implementation should guarantee that the alarm callback is never called synchronously from `set_alarm`. - /// Rather - if `timestamp` is already in the past - `false` should be returned and alarm should not be set, - /// or alternatively, the driver should return `true` and arrange to call the alarm callback as soon as possible, but not synchronously. - /// There is a rare third possibility that the alarm was barely in the future, and by the time it was enabled, it had slipped into the - /// past. This is can be detected by double-checking that the alarm is still in the future after enabling it; if it isn't, `false` - /// should also be returned to indicate that the callback may have been called already by the alarm, but it is not guaranteed, so the - /// caller should also call the callback, just like in the more common `false` case. (Note: This requires idempotency of the callback.) + /// ## Behavior /// - /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. + /// If `timestamp` is in the future, `set_alarm` schedules calling the callback function + /// at that time, and returns `true`. /// - /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any. + /// If `timestamp` is in the past, `set_alarm` has two allowed behaviors. Implementations can pick whether to: + /// + /// - Schedule calling the callback function "immediately", as if the requested timestamp was "now+epsilon" and return `true`, or + /// - Not schedule the callback, and return `false`. + /// + /// Callers must ensure to behave correctly with either behavior. + /// + /// When callback is called, it is guaranteed that `now()` will return a value greater than or equal to `timestamp`. + /// + /// ## Reentrancy + /// + /// Calling the callback from `set_alarm` synchronously is not allowed. If the implementation chooses the first option above, + /// it must still call the callback from another context (i.e. an interrupt handler or background thread), it's not allowed + /// to call it synchronously in the context `set_alarm` is running. + /// + /// The reason for the above is callers are explicitly permitted to do both of: + /// - Lock a mutex in the alarm callback. + /// - Call `set_alarm` while having that mutex locked. + /// + /// If `set_alarm` called the callback synchronously, it'd cause a deadlock or panic because it'd cause the + /// mutex to be locked twice reentrantly in the same context. + /// + /// ## Overwriting alarms + /// + /// Only one alarm can be active at a time for each `AlarmHandle`. This overwrites any previously-set alarm if any. + /// + /// ## Unsetting the alarm + /// + /// There is no `unset_alarm` API. Instead, callers can call `set_alarm` with `timestamp` set to `u64::MAX`. + /// + /// This allows for more efficient implementations, since they don't need to distinguish between the "alarm set" and + /// "alarm not set" cases, thanks to the fact "Alarm set for u64::MAX" is effectively equivalent for "alarm not set". + /// + /// This means implementations need to be careful to avoid timestamp overflows. The recommendation is to make `timestamp` + /// be in the same units as hardware ticks to avoid any conversions, which makes avoiding overflow easier. fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool; } diff --git a/embassy-time/CHANGELOG.md b/embassy-time/CHANGELOG.md index 75e17fd63..3b4d93387 100644 --- a/embassy-time/CHANGELOG.md +++ b/embassy-time/CHANGELOG.md @@ -7,7 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -## 0.4.0 - 2024-01-11 +## 0.3.2 - 2024-08-05 + +- Implement with_timeout()/with_deadline() method style call on Future +- Add collapse_debuginfo to fmt.rs macros. + +## 0.3.1 - 2024-01-11 - Add with\_deadline convenience function and example - Implement Clone for Delay diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index 5d5bd8b23..c3b4e4e3a 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-time" -version = "0.3.1" +version = "0.3.2" edition = "2021" description = "Instant and Duration for embedded no-std systems, with async timer support" repository = "https://github.com/embassy-rs/embassy" @@ -431,4 +431,4 @@ wasm-timer = { version = "0.2.5", optional = true } [dev-dependencies] serial_test = "0.9" critical-section = { version = "1.1", features = ["std"] } -embassy-executor = { version = "0.5.0", path = "../embassy-executor" } +embassy-executor = { version = "0.6.0", path = "../embassy-executor" } diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index 968fbec74..481b2699a 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -31,11 +31,11 @@ log = { version = "0.4.17", optional = true } bitflags = "2.4.1" cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } -embassy-boot = { version = "0.2.0", path = "../embassy-boot" } +embassy-boot = { version = "0.3.0", path = "../embassy-boot" } embassy-futures = { version = "0.1.1", path = "../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-time = { version = "0.3.1", path = "../embassy-time" } -embassy-usb = { version = "0.2.0", path = "../embassy-usb", default-features = false } +embassy-time = { version = "0.3.2", path = "../embassy-time" } +embassy-usb = { version = "0.3.0", path = "../embassy-usb", default-features = false } embedded-storage = { version = "0.3.1" } esp32c3-hal = { version = "0.13.0", optional = true, default-features = false } diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml index 62b4ee723..d796e5d8e 100644 --- a/embassy-usb-logger/Cargo.toml +++ b/embassy-usb-logger/Cargo.toml @@ -15,7 +15,7 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-l target = "thumbv7em-none-eabi" [dependencies] -embassy-usb = { version = "0.2.0", path = "../embassy-usb" } +embassy-usb = { version = "0.3.0", path = "../embassy-usb" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } embassy-futures = { version = "0.1.0", path = "../embassy-futures" } log = "0.4" diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 34d1ca663..11188b4ef 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -41,12 +41,24 @@ pub const MAX_PACKET_SIZE: u8 = 64; /// The logger handle, which contains a pipe with configurable size for buffering log messages. pub struct UsbLogger { buffer: Pipe, + custom_style: Option) -> ()>, } impl UsbLogger { /// Create a new logger instance. pub const fn new() -> Self { - Self { buffer: Pipe::new() } + Self { + buffer: Pipe::new(), + custom_style: None, + } + } + + /// Create a new logger instance with a custom formatter. + pub const fn with_custom_style(custom_style: fn(&Record, &mut Writer<'_, N>) -> ()) -> Self { + Self { + buffer: Pipe::new(), + custom_style: Some(custom_style), + } } /// Run the USB logger using the state and USB driver. Never returns. @@ -137,14 +149,19 @@ impl log::Log for UsbLogger { fn log(&self, record: &Record) { if self.enabled(record.metadata()) { - let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + if let Some(custom_style) = self.custom_style { + custom_style(record, &mut Writer(&self.buffer)); + } else { + let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + } } } fn flush(&self) {} } -struct Writer<'d, const N: usize>(&'d Pipe); +/// A writer that writes to the USB logger buffer. +pub struct Writer<'d, const N: usize>(&'d Pipe); impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { @@ -210,3 +227,33 @@ macro_rules! with_class { LOGGER.create_future_from_class($p) }}; } + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// This version of the macro allows for a custom style function to be passed in. +/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer. +/// +/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. +/// +/// # Usage +/// +/// ``` +/// let log_fut = embassy_usb_logger::with_custom_style!(1024, log::LevelFilter::Info, logger_class, |record, writer| { +/// use core::fmt::Write; +/// let level = record.level().as_str(); +/// write!(writer, "[{level}] {}\r\n", record.args()).unwrap(); +/// }); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_custom_style { + ( $x:expr, $l:expr, $p:ident, $s:expr ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::with_custom_style($s); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + }}; +} diff --git a/embassy-usb-synopsys-otg/src/lib.rs b/embassy-usb-synopsys-otg/src/lib.rs index b90e059f6..b145f4aa8 100644 --- a/embassy-usb-synopsys-otg/src/lib.rs +++ b/embassy-usb-synopsys-otg/src/lib.rs @@ -179,6 +179,8 @@ pub enum PhyType { /// /// Available on a few STM32 chips. InternalHighSpeed, + /// External ULPI Full-Speed PHY (or High-Speed PHY in Full-Speed mode) + ExternalFullSpeed, /// External ULPI High-Speed PHY ExternalHighSpeed, } @@ -188,14 +190,14 @@ impl PhyType { pub fn internal(&self) -> bool { match self { PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, - PhyType::ExternalHighSpeed => false, + PhyType::ExternalHighSpeed | PhyType::ExternalFullSpeed => false, } } /// Get whether this PHY is any of the high-speed types. pub fn high_speed(&self) -> bool { match self { - PhyType::InternalFullSpeed => false, + PhyType::InternalFullSpeed | PhyType::ExternalFullSpeed => false, PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, } } @@ -204,6 +206,7 @@ impl PhyType { match self { PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, + PhyType::ExternalFullSpeed => vals::Dspd::FULL_SPEED_EXTERNAL, PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, } } @@ -382,8 +385,8 @@ impl<'d, const MAX_EP_COUNT: usize> Driver<'d, MAX_EP_COUNT> { } let eps = match D::dir() { - Direction::Out => &mut self.ep_out, - Direction::In => &mut self.ep_in, + Direction::Out => &mut self.ep_out[..self.instance.endpoint_count], + Direction::In => &mut self.ep_in[..self.instance.endpoint_count], }; // Find free endpoint slot @@ -1068,6 +1071,21 @@ impl<'d> embassy_usb_driver::EndpointOut for Endpoint<'d, Out> { w.set_pktcnt(1); }); + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.doepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + // Clear NAK to indicate we are ready to receive more data self.regs.doepctl(index).modify(|w| { w.set_cnak(true); @@ -1155,6 +1173,21 @@ impl<'d> embassy_usb_driver::EndpointIn for Endpoint<'d, In> { w.set_xfrsiz(buf.len() as _); }); + if self.info.ep_type == EndpointType::Isochronous { + // Isochronous endpoints must set the correct even/odd frame bit to + // correspond with the next frame's number. + let frame_number = self.regs.dsts().read().fnsof(); + let frame_is_odd = frame_number & 0x01 == 1; + + self.regs.diepctl(index).modify(|r| { + if frame_is_odd { + r.set_sd0pid_sevnfrm(true); + } else { + r.set_sd1pid_soddfrm(true); + } + }); + } + // Enable endpoint self.regs.diepctl(index).modify(|w| { w.set_cnak(true); diff --git a/embassy-usb-synopsys-otg/src/otg_v1.rs b/embassy-usb-synopsys-otg/src/otg_v1.rs index 111bc4a63..d3abc328d 100644 --- a/embassy-usb-synopsys-otg/src/otg_v1.rs +++ b/embassy-usb-synopsys-otg/src/otg_v1.rs @@ -272,6 +272,12 @@ impl Otg { assert!(n < 12usize); unsafe { Reg::from_ptr(self.ptr.add(0x0510usize + n * 32usize) as _) } } + #[doc = "Host channel DMA address register"] + #[inline(always)] + pub const fn hcdma(self, n: usize) -> Reg { + assert!(n < 12usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0514usize + n * 32usize) as _) } + } #[doc = "Device configuration register"] #[inline(always)] pub const fn dcfg(self) -> Reg { @@ -364,6 +370,12 @@ impl Otg { assert!(n < 16usize); unsafe { Reg::from_ptr(self.ptr.add(0x0b10usize + n * 32usize) as _) } } + #[doc = "Device OUT/IN endpoint DMA address register"] + #[inline(always)] + pub const fn doepdma(self, n: usize) -> Reg { + assert!(n < 16usize); + unsafe { Reg::from_ptr(self.ptr.add(0x0b14usize + n * 32usize) as _) } + } #[doc = "Power and clock gating control register"] #[inline(always)] pub const fn pcgcctl(self) -> Reg { @@ -783,15 +795,15 @@ pub mod regs { pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); } - #[doc = "SODDFRM/SD1PID"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub const fn soddfrm_sd1pid(&self) -> bool { + pub const fn sd1pid_soddfrm(&self) -> bool { let val = (self.0 >> 29usize) & 0x01; val != 0 } - #[doc = "SODDFRM/SD1PID"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub fn set_soddfrm_sd1pid(&mut self, val: bool) { + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); } #[doc = "EPDIS"] @@ -1162,15 +1174,15 @@ pub mod regs { pub fn set_sd0pid_sevnfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 28usize)) | (((val as u32) & 0x01) << 28usize); } - #[doc = "SODDFRM"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub const fn soddfrm(&self) -> bool { + pub const fn sd1pid_soddfrm(&self) -> bool { let val = (self.0 >> 29usize) & 0x01; val != 0 } - #[doc = "SODDFRM"] + #[doc = "SD1PID/SODDFRM"] #[inline(always)] - pub fn set_soddfrm(&mut self, val: bool) { + pub fn set_sd1pid_soddfrm(&mut self, val: bool) { self.0 = (self.0 & !(0x01 << 29usize)) | (((val as u32) & 0x01) << 29usize); } #[doc = "EPDIS"] diff --git a/embassy-usb/CHANGELOG.md b/embassy-usb/CHANGELOG.md index 5f665ed25..efdda96fb 100644 --- a/embassy-usb/CHANGELOG.md +++ b/embassy-usb/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 0.3.0 - 2024-08-05 + +- bump usbd-hid from 0.7.0 to 0.8.1 +- Add collapse_debuginfo to fmt.rs macros. +- update embassy-sync dependency + ## 0.2.0 - 2024-05-20 - [#2862](https://github.com/embassy-rs/embassy/pull/2862) WebUSB implementation by @chmanie diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 1cc9cf64e..e310d8bae 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "embassy-usb" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT OR Apache-2.0" description = "Async USB device stack for embedded devices in Rust." @@ -49,7 +49,7 @@ max-handler-count-8 = [] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } embassy-sync = { version = "0.6.0", path = "../embassy-sync" } -embassy-net-driver-channel = { version = "0.2.0", path = "../embassy-net-driver-channel" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 7168e077c..e1bf8041f 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,8 +1,8 @@ use heapless::Vec; use crate::config::MAX_HANDLER_COUNT; -use crate::descriptor::{BosWriter, DescriptorWriter}; -use crate::driver::{Driver, Endpoint, EndpointType}; +use crate::descriptor::{BosWriter, DescriptorWriter, SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointInfo, EndpointType}; use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; use crate::types::{InterfaceNumber, StringIndex}; use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; @@ -414,7 +414,7 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn descriptor(&mut self, descriptor_type: u8, descriptor: &[u8]) { - self.builder.config_descriptor.write(descriptor_type, descriptor); + self.builder.config_descriptor.write(descriptor_type, descriptor, &[]); } /// Add a custom Binary Object Store (BOS) descriptor to this alternate setting. @@ -422,26 +422,80 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { self.builder.bos_descriptor.capability(capability_type, capability); } - fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + /// Write a custom endpoint descriptor for a certain endpoint. + /// + /// This can be necessary, if the endpoint descriptors can only be written + /// after the endpoint was created. As an example, an endpoint descriptor + /// may contain the address of an endpoint that was allocated earlier. + pub fn endpoint_descriptor( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { + self.builder + .config_descriptor + .endpoint(endpoint, synchronization_type, usage_type, extra_fields); + } + + /// Allocate an IN endpoint, without writing its descriptor. + /// + /// Used for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { let ep = self .builder .driver .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_in failed"); - self.builder.config_descriptor.endpoint(ep.info()); + ep + } + + fn endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + let ep = self.alloc_endpoint_in(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); ep } - fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + /// Allocate an OUT endpoint, without writing its descriptor. + /// + /// Use for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> D::EndpointOut { let ep = self .builder .driver .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_out failed"); - self.builder.config_descriptor.endpoint(ep.info()); + ep + } + + fn endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + let ep = self.alloc_endpoint_out(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); ep } @@ -451,7 +505,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_bulk_in(&mut self, max_packet_size: u16) -> D::EndpointIn { - self.endpoint_in(EndpointType::Bulk, max_packet_size, 0) + self.endpoint_in( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a BULK OUT endpoint and write its descriptor. @@ -459,7 +520,14 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_bulk_out(&mut self, max_packet_size: u16) -> D::EndpointOut { - self.endpoint_out(EndpointType::Bulk, max_packet_size, 0) + self.endpoint_out( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a INTERRUPT IN endpoint and write its descriptor. @@ -467,24 +535,66 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms) + self.endpoint_in( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a INTERRUPT OUT endpoint and write its descriptor. pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms) + self.endpoint_out( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) } /// Allocate a ISOCHRONOUS IN endpoint and write its descriptor. /// /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. - pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms) + pub fn endpoint_isochronous_in( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) } /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. - pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms) + pub fn endpoint_isochronous_out( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) } } diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index eb3d1f53a..06ebe0481 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,4 +1,5 @@ //! Utilities for writing USB descriptors. +use embassy_usb_driver::EndpointType; use crate::builder::Config; use crate::driver::EndpointInfo; @@ -13,6 +14,8 @@ pub mod descriptor_type { pub const STRING: u8 = 3; pub const INTERFACE: u8 = 4; pub const ENDPOINT: u8 = 5; + pub const DEVICE_QUALIFIER: u8 = 6; + pub const OTHER_SPEED_CONFIGURATION: u8 = 7; pub const IAD: u8 = 11; pub const BOS: u8 = 15; pub const CAPABILITY: u8 = 16; @@ -36,6 +39,40 @@ pub mod capability_type { pub const PLATFORM: u8 = 5; } +/// USB endpoint synchronization type. The values of this enum can be directly +/// cast into `u8` to get the bmAttributes synchronization type bits. +/// Values other than `NoSynchronization` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SynchronizationType { + /// No synchronization is used. + NoSynchronization = 0b00, + /// Unsynchronized, although sinks provide data rate feedback. + Asynchronous = 0b01, + /// Synchronized using feedback or feedforward data rate information. + Adaptive = 0b10, + /// Synchronized to the USB’s SOF. + Synchronous = 0b11, +} + +/// USB endpoint usage type. The values of this enum can be directly cast into +/// `u8` to get the bmAttributes usage type bits. +/// Values other than `DataEndpoint` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsageType { + /// Use the endpoint for regular data transfer. + DataEndpoint = 0b00, + /// Endpoint conveys explicit feedback information for one or more data endpoints. + FeedbackEndpoint = 0b01, + /// A data endpoint that also serves as an implicit feedback endpoint for one or more data endpoints. + ImplicitFeedbackDataEndpoint = 0b10, + /// Reserved usage type. + Reserved = 0b11, +} + /// A writer for USB descriptors. pub(crate) struct DescriptorWriter<'a> { pub buf: &'a mut [u8], @@ -63,23 +100,26 @@ impl<'a> DescriptorWriter<'a> { self.position } - /// Writes an arbitrary (usually class-specific) descriptor. - pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8]) { - let length = descriptor.len(); + /// Writes an arbitrary (usually class-specific) descriptor with optional extra fields. + pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8], extra_fields: &[u8]) { + let descriptor_length = descriptor.len(); + let extra_fields_length = extra_fields.len(); + let total_length = descriptor_length + extra_fields_length; assert!( - (self.position + 2 + length) <= self.buf.len() && (length + 2) <= 255, + (self.position + 2 + total_length) <= self.buf.len() && (total_length + 2) <= 255, "Descriptor buffer full" ); - self.buf[self.position] = (length + 2) as u8; + self.buf[self.position] = (total_length + 2) as u8; self.buf[self.position + 1] = descriptor_type; let start = self.position + 2; - self.buf[start..start + length].copy_from_slice(descriptor); + self.buf[start..start + descriptor_length].copy_from_slice(descriptor); + self.buf[start + descriptor_length..start + total_length].copy_from_slice(extra_fields); - self.position = start + length; + self.position = start + total_length; } pub(crate) fn configuration(&mut self, config: &Config) { @@ -97,6 +137,7 @@ impl<'a> DescriptorWriter<'a> { | if config.supports_remote_wakeup { 0x20 } else { 0x00 }, // bmAttributes (config.max_power / 2) as u8, // bMaxPower ], + &[], ); } @@ -143,6 +184,7 @@ impl<'a> DescriptorWriter<'a> { function_protocol, 0, ], + &[], ); } @@ -193,6 +235,7 @@ impl<'a> DescriptorWriter<'a> { interface_protocol, // bInterfaceProtocol str_index, // iInterface ], + &[], ); } @@ -202,21 +245,50 @@ impl<'a> DescriptorWriter<'a> { /// /// * `endpoint` - Endpoint previously allocated with /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). - pub fn endpoint(&mut self, endpoint: &EndpointInfo) { + /// * `synchronization_type` - The synchronization type of the endpoint. + /// * `usage_type` - The usage type of the endpoint. + /// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor. + pub fn endpoint( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { match self.num_endpoints_mark { Some(mark) => self.buf[mark] += 1, None => panic!("you can only call `endpoint` after `interface/interface_alt`."), }; + let mut bm_attributes = endpoint.ep_type as u8; + + // Synchronization types other than `NoSynchronization`, + // and usage types other than `DataEndpoint` + // are only allowed for isochronous endpoints. + if endpoint.ep_type != EndpointType::Isochronous { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization); + assert_eq!(usage_type, UsageType::DataEndpoint); + } else { + if usage_type == UsageType::FeedbackEndpoint { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization) + } + + let synchronization_bm_attibutes: u8 = (synchronization_type as u8) << 2; + let usage_bm_attibutes: u8 = (usage_type as u8) << 4; + + bm_attributes |= usage_bm_attibutes | synchronization_bm_attibutes; + } + self.write( descriptor_type::ENDPOINT, &[ - endpoint.addr.into(), // bEndpointAddress - endpoint.ep_type as u8, // bmAttributes + endpoint.addr.into(), // bEndpointAddress + bm_attributes, // bmAttributes endpoint.max_packet_size as u8, (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize endpoint.interval_ms, // bInterval ], + extra_fields, ); } @@ -272,6 +344,25 @@ pub(crate) fn device_descriptor(config: &Config) -> [u8; 18] { ] } +/// Create a new Device Qualifier Descriptor array. +/// +/// All device qualifier descriptors are always 10 bytes, so there's no need for +/// a variable-length buffer or DescriptorWriter. +pub(crate) fn device_qualifier_descriptor(config: &Config) -> [u8; 10] { + [ + 10, // bLength + 0x06, // bDescriptorType + 0x10, + 0x02, // bcdUSB 2.1 + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 + 1, // bNumConfigurations + 0, // Reserved + ] +} + /// A writer for Binary Object Store descriptor. pub struct BosWriter<'a> { pub(crate) writer: DescriptorWriter<'a>, @@ -287,6 +378,9 @@ impl<'a> BosWriter<'a> { } pub(crate) fn bos(&mut self) { + if (self.writer.buf.len() - self.writer.position) < 5 { + return; + } self.num_caps_mark = Some(self.writer.position + 4); self.writer.write( descriptor_type::BOS, @@ -294,6 +388,7 @@ impl<'a> BosWriter<'a> { 0x00, 0x00, // wTotalLength 0x00, // bNumDeviceCaps ], + &[], ); self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]); @@ -329,6 +424,9 @@ impl<'a> BosWriter<'a> { } pub(crate) fn end_bos(&mut self) { + if self.writer.position == 0 { + return; + } self.num_caps_mark = None; let position = self.writer.position as u16; self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes()); diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index d58950838..a5478ca0e 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -190,6 +190,7 @@ struct Inner<'d, D: Driver<'d>> { config: Config<'d>, device_descriptor: [u8; 18], + device_qualifier_descriptor: [u8; 10], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, @@ -225,6 +226,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { // This prevent further allocation by consuming the driver. let (bus, control) = driver.start(config.max_packet_size_0 as u16); let device_descriptor = descriptor::device_descriptor(&config); + let device_qualifier_descriptor = descriptor::device_qualifier_descriptor(&config); Self { control_buf, @@ -233,6 +235,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { bus, config, device_descriptor, + device_qualifier_descriptor, config_descriptor, bos_descriptor, msos_descriptor, @@ -764,6 +767,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } } } + descriptor_type::DEVICE_QUALIFIER => InResponse::Accepted(&self.device_qualifier_descriptor), _ => InResponse::Rejected, } } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs index 25936d084..9f4e1a57b 100644 --- a/embassy-usb/src/msos.rs +++ b/embassy-usb/src/msos.rs @@ -278,6 +278,7 @@ pub enum DescriptorType { /// Table 5. Descriptor set information structure. #[allow(non_snake_case)] +#[allow(unused)] #[repr(C, packed(1))] pub struct DescriptorSetInformation { dwWindowsVersion: u32, @@ -288,6 +289,7 @@ pub struct DescriptorSetInformation { /// Table 4. Microsoft OS 2.0 platform capability descriptor header. #[allow(non_snake_case)] +#[allow(unused)] #[repr(C, packed(1))] pub struct PlatformDescriptor { bLength: u8, diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index dbbe0fddc..93e49faef 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -6,12 +6,12 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [] } -embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } -embassy-boot = { version = "0.2.0", path = "../../../../embassy-boot", features = [] } -embassy-boot-nrf = { version = "0.2.0", path = "../../../../embassy-boot-nrf", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } +embassy-nrf = { version = "0.2.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", ] } +embassy-boot = { version = "0.3.0", path = "../../../../embassy-boot", features = [] } +embassy-boot-nrf = { version = "0.3.0", path = "../../../../embassy-boot-nrf", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/nrf/build.rs b/examples/boot/application/nrf/build.rs index cd1a264c4..e1da69328 100644 --- a/examples/boot/application/nrf/build.rs +++ b/examples/boot/application/nrf/build.rs @@ -31,4 +31,7 @@ fn main() { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } } diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index 851a3d721..2c1d1a7bb 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -2,6 +2,9 @@ #![no_main] #![macro_use] +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot::State; use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; @@ -22,6 +25,7 @@ async fn main(_spawner: Spawner) { let mut button = Input::new(p.P0_11, Pull::Up); let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + let mut led_reverted = Output::new(p.P0_14, Level::High, OutputDrive::Standard); //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); //let mut button = Input::new(p.P1_02, Pull::Up); @@ -53,6 +57,13 @@ async fn main(_spawner: Spawner) { let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc); let mut magic = [0; 4]; let mut updater = FirmwareUpdater::new(config, &mut magic); + let state = updater.get_state().await.unwrap(); + if state == State::Revert { + led_reverted.set_low(); + } else { + led_reverted.set_high(); + } + loop { led.set_low(); button.wait_for_any_edge().await; diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml index d4341e8f6..8bb8afdfe 100644 --- a/examples/boot/application/rp/Cargo.toml +++ b/examples/boot/application/rp/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [] } -embassy-rp = { version = "0.1.0", path = "../../../../embassy-rp", features = ["time-driver", ] } -embassy-boot-rp = { version = "0.2.0", path = "../../../../embassy-boot-rp", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-16384", "arch-cortex-m", "executor-thread", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [] } +embassy-rp = { version = "0.2.0", path = "../../../../embassy-rp", features = ["time-driver", "rp2040"] } +embassy-boot-rp = { version = "0.3.0", path = "../../../../embassy-boot-rp", features = [] } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index c203ffc9f..1c2934298 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f303re", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32" } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index ed13eab65..09e34c7df 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32f767zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index f25e9815d..5e7f4d5e7 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32h743zi", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index c1a47dfe4..60fdcfafb 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l072cz", "time-driver-any", "exti", "memory-x"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index 1e83d3113..fe3ab2c04 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l151cb-a", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index bca292681..169856358 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32l475vg", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 0484e6ceb..7cef8fe0d 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -6,12 +6,12 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } -embassy-usb = { version = "0.2.0", path = "../../../../embassy-usb" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } +embassy-usb = { version = "0.3.0", path = "../../../../embassy-usb" } embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } defmt = { version = "0.3", optional = true } diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index b785a1968..860a835a9 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -6,11 +6,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wl55jc-cm4", "time-driver-any", "exti"] } embassy-boot-stm32 = { version = "0.2.0", path = "../../../../embassy-boot-stm32", features = [] } -embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-embedded-hal = { version = "0.2.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32wl/memory.x b/examples/boot/application/stm32wl/memory.x index e1d4e7fa8..20109e37e 100644 --- a/examples/boot/application/stm32wl/memory.x +++ b/examples/boot/application/stm32wl/memory.x @@ -5,7 +5,8 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K FLASH : ORIGIN = 0x08008000, LENGTH = 64K DFU : ORIGIN = 0x08018000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K + SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128 + RAM (rwx) : ORIGIN = 0x20000080, LENGTH = 32K - 128 } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); @@ -13,3 +14,11 @@ __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - O __bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); __bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); + +SECTIONS +{ + .shared_data : + { + *(.shared_data) + } > SHARED_RAM +} diff --git a/examples/boot/application/stm32wl/src/bin/a.rs b/examples/boot/application/stm32wl/src/bin/a.rs index 9f4f0b238..127de0237 100644 --- a/examples/boot/application/stm32wl/src/bin/a.rs +++ b/examples/boot/application/stm32wl/src/bin/a.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + #[cfg(feature = "defmt")] use defmt_rtt::*; use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; @@ -9,6 +11,7 @@ use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::SharedData; use embassy_sync::mutex::Mutex; use panic_reset as _; @@ -17,9 +20,12 @@ 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"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); let flash = Flash::new_blocking(p.FLASH); let flash = Mutex::new(BlockingAsync::new(flash)); diff --git a/examples/boot/application/stm32wl/src/bin/b.rs b/examples/boot/application/stm32wl/src/bin/b.rs index e954d8b91..768dadf8b 100644 --- a/examples/boot/application/stm32wl/src/bin/b.rs +++ b/examples/boot/application/stm32wl/src/bin/b.rs @@ -1,16 +1,22 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + #[cfg(feature = "defmt")] use defmt_rtt::*; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; use embassy_time::Timer; use panic_reset as _; +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); let mut led = Output::new(p.PB15, Level::High, Speed::Low); loop { diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml index c15c980ca..9df396e5e 100644 --- a/examples/boot/bootloader/rp/Cargo.toml +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } -embassy-rp = { path = "../../../../embassy-rp", features = [] } +embassy-rp = { path = "../../../../embassy-rp", features = ["rp2040"] } embassy-boot-rp = { path = "../../../../embassy-boot-rp" } embassy-sync = { version = "0.6.0", path = "../../../../embassy-sync" } embassy-time = { path = "../../../../embassy-time", features = [] } diff --git a/examples/boot/bootloader/rp/memory.x b/examples/boot/bootloader/rp/memory.x index c3b54976e..88b5bbb15 100644 --- a/examples/boot/bootloader/rp/memory.x +++ b/examples/boot/bootloader/rp/memory.x @@ -2,7 +2,7 @@ MEMORY { /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 - FLASH : ORIGIN = 0x10000100, LENGTH = 24K + FLASH : ORIGIN = 0x10000100, LENGTH = 24K - 0x100 BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K ACTIVE : ORIGIN = 0x10007000, LENGTH = 512K DFU : ORIGIN = 0x10087000, LENGTH = 516K diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index 9950ed7b6..050b672ce 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -18,7 +18,7 @@ embedded-storage = "0.3.1" embedded-storage-async = "0.4.0" cfg-if = "1.0.0" embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["dfu", "cortex-m"] } -embassy-usb = { version = "0.2.0", path = "../../../../embassy-usb", default-features = false } +embassy-usb = { version = "0.3.0", path = "../../../../embassy-usb", default-features = false } embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" } [features] diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml index 70a89bb30..98a678815 100644 --- a/examples/nrf-rtos-trace/Cargo.toml +++ b/examples/nrf-rtos-trace/Cargo.toml @@ -16,9 +16,9 @@ log = [ [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time" } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time" } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/nrf51/Cargo.toml b/examples/nrf51/Cargo.toml index c52256d8e..93a19bea7 100644 --- a/examples/nrf51/Cargo.toml +++ b/examples/nrf51/Cargo.toml @@ -5,9 +5,9 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/nrf52810/Cargo.toml b/examples/nrf52810/Cargo.toml index 2031b253f..0e3e81c3f 100644 --- a/examples/nrf52810/Cargo.toml +++ b/examples/nrf52810/Cargo.toml @@ -7,9 +7,9 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-8192", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52810", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/nrf52840-rtic/Cargo.toml b/examples/nrf52840-rtic/Cargo.toml index 731cee843..7fae7aefc 100644 --- a/examples/nrf52840-rtic/Cargo.toml +++ b/examples/nrf52840-rtic/Cargo.toml @@ -9,8 +9,8 @@ rtic = { version = "2", features = ["thumbv7-backend"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime", "generic-queue"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = [ "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = [ "defmt", "defmt-timestamp-uptime", "generic-queue"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = [ "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index ff90d13af..17fa6234d 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -7,11 +7,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embedded-io = { version = "0.6.0", features = ["defmt-03"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } diff --git a/examples/nrf52840/src/bin/channel.rs b/examples/nrf52840/src/bin/channel.rs index 7fcea9dbd..e06ba1c73 100644 --- a/examples/nrf52840/src/bin/channel.rs +++ b/examples/nrf52840/src/bin/channel.rs @@ -35,8 +35,8 @@ async fn main(spawner: Spawner) { loop { match CHANNEL.receive().await { - LedState::On => led.set_high(), - LedState::Off => led.set_low(), + LedState::On => led.set_low(), + LedState::Off => led.set_high(), } } } diff --git a/examples/nrf52840/src/bin/channel_sender_receiver.rs b/examples/nrf52840/src/bin/channel_sender_receiver.rs index 3095a04ec..29f70f91c 100644 --- a/examples/nrf52840/src/bin/channel_sender_receiver.rs +++ b/examples/nrf52840/src/bin/channel_sender_receiver.rs @@ -33,8 +33,8 @@ async fn recv_task(led: AnyPin, receiver: Receiver<'static, NoopRawMutex, LedSta loop { match receiver.receive().await { - LedState::On => led.set_high(), - LedState::Off => led.set_low(), + LedState::On => led.set_low(), + LedState::Off => led.set_high(), } } } diff --git a/examples/nrf52840/src/bin/ethernet_enc28j60.rs b/examples/nrf52840/src/bin/ethernet_enc28j60.rs index 279f32edc..0946492fe 100644 --- a/examples/nrf52840/src/bin/ethernet_enc28j60.rs +++ b/examples/nrf52840/src/bin/ethernet_enc28j60.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_enc28j60::Enc28j60; use embassy_nrf::gpio::{Level, Output, OutputDrive}; use embassy_nrf::rng::Rng; @@ -23,11 +23,12 @@ bind_interrupts!(struct Irqs { #[embassy_executor::task] async fn net_task( - stack: &'static Stack< + mut runner: embassy_net::Runner< + 'static, Enc28j60, Output<'static>, Delay>, Output<'static>>, >, ) -> ! { - stack.run().await + runner.run().await } #[embassy_executor::main] @@ -66,18 +67,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell< - Stack, Output<'static>, Delay>, Output<'static>>>, - > = StaticCell::new(); - let stack = STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs index a7e5c2668..b07adac1f 100644 --- a/examples/nrf52840/src/bin/usb_ethernet.rs +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -6,7 +6,7 @@ use core::mem; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_nrf::rng::Rng; use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; use embassy_nrf::usb::Driver; @@ -39,8 +39,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -115,11 +115,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell>> = StaticCell::new(); - let stack = &*STACK.init(Stack::new(device, config, RESOURCES.init(StackResources::new()), seed)); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs index 00bd50081..26eaf485e 100644 --- a/examples/nrf52840/src/bin/wifi_esp_hosted.rs +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -4,7 +4,7 @@ use defmt::{info, unwrap, warn}; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; @@ -36,8 +36,8 @@ async fn wifi_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, hosted::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -89,16 +89,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static RESOURCES: StaticCell> = StaticCell::new(); - static STACK: StaticCell>> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index aeed39b3d..0da85be07 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -7,11 +7,11 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf5340-app-s", "time-driver-rtc1", "gpiote", "unstable-pac"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embedded-io-async = { version = "0.6.1" } defmt = "0.3" diff --git a/examples/nrf9151/ns/Cargo.toml b/examples/nrf9151/ns/Cargo.toml index d73f17e87..17fe27b67 100644 --- a/examples/nrf9151/ns/Cargo.toml +++ b/examples/nrf9151/ns/Cargo.toml @@ -5,9 +5,9 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.5.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-ns", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/nrf9151/s/Cargo.toml b/examples/nrf9151/s/Cargo.toml index 164f9c94e..7253fc4be 100644 --- a/examples/nrf9151/s/Cargo.toml +++ b/examples/nrf9151/s/Cargo.toml @@ -5,9 +5,9 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.5.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.6.0", path = "../../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../../embassy-nrf", features = ["defmt", "nrf9120-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/nrf9160/.cargo/config.toml b/examples/nrf9160/.cargo/config.toml index f64c63966..6072b8595 100644 --- a/examples/nrf9160/.cargo/config.toml +++ b/examples/nrf9160/.cargo/config.toml @@ -1,5 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-rs run --chip nRF9160_xxAA" +# runner = "probe-rs run --chip nRF9160_xxAA" +runner = [ "probe-rs", "run", "--chip=nRF9160_xxAA", "--always-print-stacktrace", "--log-format={t} {[{L}]%bold} {s} {{c} {ff}:{l:1}%dimmed}" ] [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/nrf9160/Cargo.toml b/examples/nrf9160/Cargo.toml index 2ff692b24..9aeb99317 100644 --- a/examples/nrf9160/Cargo.toml +++ b/examples/nrf9160/Cargo.toml @@ -5,16 +5,22 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "nrf9160-s", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net-nrf91 = { version = "0.1.0", path = "../../embassy-net-nrf91", features = ["defmt"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "proto-ipv4", "medium-ip"] } defmt = "0.3" defmt-rtt = "0.4" +heapless = "0.8" cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } +static_cell = { version = "2" } +embedded-io = "0.6.1" +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } [profile.release] debug = 2 diff --git a/examples/nrf9160/memory.x b/examples/nrf9160/memory.x index 4c7d4ebf0..e33498773 100644 --- a/examples/nrf9160/memory.x +++ b/examples/nrf9160/memory.x @@ -1,5 +1,9 @@ MEMORY { - FLASH : ORIGIN = 0x00000000, LENGTH = 1024K - RAM : ORIGIN = 0x20018000, LENGTH = 160K + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20010000, LENGTH = 192K + IPC : ORIGIN = 0x20000000, LENGTH = 64K } + +PROVIDE(__start_ipc = ORIGIN(IPC)); +PROVIDE(__end_ipc = ORIGIN(IPC) + LENGTH(IPC)); diff --git a/examples/nrf9160/src/bin/modem_tcp_client.rs b/examples/nrf9160/src/bin/modem_tcp_client.rs new file mode 100644 index 000000000..929883884 --- /dev/null +++ b/examples/nrf9160/src/bin/modem_tcp_client.rs @@ -0,0 +1,198 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; +use core::net::IpAddr; +use core::ptr::addr_of_mut; +use core::slice; +use core::str::FromStr; + +use defmt::{info, unwrap, warn}; +use embassy_executor::Spawner; +use embassy_net::{Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net_nrf91::context::Status; +use embassy_net_nrf91::{context, Runner, State, TraceBuffer, TraceReader}; +use embassy_nrf::buffered_uarte::{self, BufferedUarteTx}; +use embassy_nrf::gpio::{AnyPin, Level, Output, OutputDrive, Pin}; +use embassy_nrf::uarte::Baudrate; +use embassy_nrf::{bind_interrupts, interrupt, peripherals, uarte}; +use embassy_time::{Duration, Timer}; +use embedded_io_async::Write; +use heapless::Vec; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[interrupt] +fn IPC() { + embassy_net_nrf91::on_ipc_irq(); +} + +bind_interrupts!(struct Irqs { + UARTE0_SPIM0_SPIS0_TWIM0_TWIS0 => buffered_uarte::InterruptHandler; +}); + +#[embassy_executor::task] +async fn trace_task(mut uart: BufferedUarteTx<'static, peripherals::SERIAL0>, reader: TraceReader<'static>) -> ! { + let mut rx = [0u8; 1024]; + loop { + let n = reader.read(&mut rx[..]).await; + unwrap!(uart.write_all(&rx[..n]).await); + } +} + +#[embassy_executor::task] +async fn modem_task(runner: Runner<'static>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_nrf91::NetDriver<'static>>) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn control_task( + control: &'static context::Control<'static>, + config: context::Config<'static>, + stack: Stack<'static>, +) { + unwrap!(control.configure(&config).await); + unwrap!( + control + .run(|status| { + stack.set_config_v4(status_to_config(status)); + }) + .await + ); +} + +fn status_to_config(status: &Status) -> embassy_net::ConfigV4 { + let Some(IpAddr::V4(addr)) = status.ip else { + panic!("Unexpected IP address"); + }; + let addr = Ipv4Address(addr.octets()); + + let gateway = if let Some(IpAddr::V4(addr)) = status.gateway { + Some(Ipv4Address(addr.octets())) + } else { + None + }; + + let mut dns_servers = Vec::new(); + for dns in status.dns.iter() { + if let IpAddr::V4(ip) = dns { + unwrap!(dns_servers.push(Ipv4Address(ip.octets()))); + } + } + + embassy_net::ConfigV4::Static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(addr, 32), + gateway, + dns_servers, + }) +} + +#[embassy_executor::task] +async fn blink_task(pin: AnyPin) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + loop { + led.set_high(); + Timer::after_millis(1000).await; + led.set_low(); + Timer::after_millis(1000).await; + } +} + +extern "C" { + static __start_ipc: u8; + static __end_ipc: u8; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + info!("Hello World!"); + + unwrap!(spawner.spawn(blink_task(p.P0_02.degrade()))); + + let ipc_mem = unsafe { + let ipc_start = &__start_ipc as *const u8 as *mut MaybeUninit; + let ipc_end = &__end_ipc as *const u8 as *mut MaybeUninit; + let ipc_len = ipc_end.offset_from(ipc_start) as usize; + slice::from_raw_parts_mut(ipc_start, ipc_len) + }; + + static mut TRACE_BUF: [u8; 4096] = [0u8; 4096]; + let mut config = uarte::Config::default(); + config.baudrate = Baudrate::BAUD1M; + let uart = BufferedUarteTx::new( + //let trace_uart = BufferedUarteTx::new( + unsafe { peripherals::SERIAL0::steal() }, + Irqs, + unsafe { peripherals::P0_01::steal() }, + //unsafe { peripherals::P0_14::steal() }, + config, + unsafe { &mut *addr_of_mut!(TRACE_BUF) }, + ); + + static STATE: StaticCell = StaticCell::new(); + static TRACE: StaticCell = StaticCell::new(); + let (device, control, runner, tracer) = + embassy_net_nrf91::new_with_trace(STATE.init(State::new()), ipc_mem, TRACE.init(TraceBuffer::new())).await; + unwrap!(spawner.spawn(modem_task(runner))); + unwrap!(spawner.spawn(trace_task(uart, tracer))); + + let config = embassy_net::Config::default(); + + // Generate "random" seed. nRF91 has no RNG, TODO figure out something... + let seed = 123456; + + // Init network stack + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::<2>::new()), seed); + + unwrap!(spawner.spawn(net_task(runner))); + + static CONTROL: StaticCell> = StaticCell::new(); + let control = CONTROL.init(context::Control::new(control, 0).await); + + unwrap!(spawner.spawn(control_task( + control, + context::Config { + apn: b"iot.nat.es", + auth_prot: context::AuthProt::Pap, + auth: Some((b"orange", b"orange")), + }, + stack + ))); + + stack.wait_config_up().await; + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("45.79.112.203").unwrap(); + if let Err(e) = socket.connect((host_addr, 4242)).await { + warn!("connect error: {:?}", e); + Timer::after_secs(10).await; + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + + let msg = b"Hello world!\n"; + for _ in 0..10 { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after_secs(1).await; + } + Timer::after_secs(4).await; + } +} diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index 604112546..04b4c6317 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -6,18 +6,18 @@ license = "MIT OR Apache-2.0" [dependencies] -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } -cyw43 = { version = "0.1.0", path = "../../cyw43", features = ["defmt", "firmware-logs"] } -cyw43-pio = { version = "0.1.0", path = "../../cyw43-pio", features = ["defmt", "overclock"] } +cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } defmt = "0.3" defmt-rtt = "0.4" @@ -51,7 +51,7 @@ embedded-hal-async = "1.0" embedded-hal-bus = { version = "0.1", features = ["async"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } embedded-storage = { version = "0.3" } -static_cell = "2" +static_cell = "2.1" portable-atomic = { version = "1.5", features = ["critical-section"] } log = "0.4" pio-proc = "0.2" @@ -59,9 +59,24 @@ pio = "0.2.1" rand = { version = "0.8.5", default-features = false } embedded-sdmmc = "0.7.0" +bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } +trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } + [profile.release] debug = 2 +lto = true +opt-level = 'z' [profile.dev] +debug = 2 lto = true opt-level = "z" + +[patch.crates-io] +trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } +embassy-executor = { path = "../../embassy-executor" } +embassy-sync = { path = "../../embassy-sync" } +embassy-futures = { path = "../../embassy-futures" } +embassy-time = { path = "../../embassy-time" } +embassy-time-driver = { path = "../../embassy-time-driver" } +embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/examples/rp/src/bin/bluetooth.rs b/examples/rp/src/bin/bluetooth.rs new file mode 100644 index 000000000..7524e7929 --- /dev/null +++ b/examples/rp/src/bin/bluetooth.rs @@ -0,0 +1,150 @@ +//! This example test the RP Pico W on board LED. +//! +//! It does not work with the RP Pico board. See blinky.rs. + +#![no_std] +#![no_main] + +use bt_hci::controller::ExternalController; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join3; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use trouble_host::advertise::{AdStructure, Advertisement, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE}; +use trouble_host::attribute::{AttributeTable, CharacteristicProp, Service, Uuid}; +use trouble_host::gatt::GattEvent; +use trouble_host::{Address, BleHost, BleHostResources, PacketQos}; +use {defmt_rtt as _, embassy_time as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + let btfw = include_bytes!("../../../../cyw43-firmware/43439A0_btfw.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + // probe-rs download 43439A0_btfw.bin --format bin --chip RP2040 --base-address 0x10141400 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + //let btfw = unsafe { core::slice::from_raw_parts(0x10141400 as *const u8, 6164) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + let (_net_device, bt_device, mut control, runner) = cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw).await; + unwrap!(spawner.spawn(cyw43_task(runner))); + control.init(clm).await; + + let controller: ExternalController<_, 10> = ExternalController::new(bt_device); + static HOST_RESOURCES: StaticCell> = StaticCell::new(); + let host_resources = HOST_RESOURCES.init(BleHostResources::new(PacketQos::None)); + + let mut ble: BleHost<'_, _> = BleHost::new(controller, host_resources); + + ble.set_random_address(Address::random([0xff, 0x9f, 0x1a, 0x05, 0xe4, 0xff])); + let mut table: AttributeTable<'_, NoopRawMutex, 10> = AttributeTable::new(); + + // Generic Access Service (mandatory) + let id = b"Pico W Bluetooth"; + let appearance = [0x80, 0x07]; + let mut bat_level = [0; 1]; + let handle = { + let mut svc = table.add_service(Service::new(0x1800)); + let _ = svc.add_characteristic_ro(0x2a00, id); + let _ = svc.add_characteristic_ro(0x2a01, &appearance[..]); + svc.build(); + + // Generic attribute service (mandatory) + table.add_service(Service::new(0x1801)); + + // Battery service + let mut svc = table.add_service(Service::new(0x180f)); + + svc.add_characteristic( + 0x2a19, + &[CharacteristicProp::Read, CharacteristicProp::Notify], + &mut bat_level, + ) + .build() + }; + + let mut adv_data = [0; 31]; + AdStructure::encode_slice( + &[ + AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), + AdStructure::ServiceUuids16(&[Uuid::Uuid16([0x0f, 0x18])]), + AdStructure::CompleteLocalName(b"Pico W Bluetooth"), + ], + &mut adv_data[..], + ) + .unwrap(); + + let server = ble.gatt_server(&table); + + info!("Starting advertising and GATT service"); + let _ = join3( + ble.run(), + async { + loop { + match server.next().await { + Ok(GattEvent::Write { handle, connection: _ }) => { + let _ = table.get(handle, |value| { + info!("Write event. Value written: {:?}", value); + }); + } + Ok(GattEvent::Read { .. }) => { + info!("Read event"); + } + Err(e) => { + error!("Error processing GATT events: {:?}", e); + } + } + } + }, + async { + let mut advertiser = ble + .advertise( + &Default::default(), + Advertisement::ConnectableScannableUndirected { + adv_data: &adv_data[..], + scan_data: &[], + }, + ) + .await + .unwrap(); + let conn = advertiser.accept().await.unwrap(); + // Keep connection alive + let mut tick: u8 = 0; + loop { + Timer::after(Duration::from_secs(10)).await; + tick += 1; + server.notify(handle, &conn, &[tick]).await.unwrap(); + } + }, + ) + .await; +} diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index def26b53d..12003adbe 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -36,8 +36,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -71,17 +71,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), - RESOURCES.init(StackResources::<3>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -89,12 +88,12 @@ async fn main(spawner: Spawner) { info!("IP address: {:?}", local_addr); // Create two sockets listening to the same port, to handle simultaneous connections - unwrap!(spawner.spawn(listen_task(&stack, 0, 1234))); - unwrap!(spawner.spawn(listen_task(&stack, 1, 1234))); + unwrap!(spawner.spawn(listen_task(stack, 0, 1234))); + unwrap!(spawner.spawn(listen_task(stack, 1, 1234))); } #[embassy_executor::task(pool_size = 2)] -async fn listen_task(stack: &'static Stack>, id: u8, port: u16) { +async fn listen_task(stack: Stack<'static>, id: u8, port: u16) { let mut rx_buffer = [0; 4096]; let mut tx_buffer = [0; 4096]; let mut buf = [0; 4096]; @@ -131,7 +130,7 @@ async fn listen_task(stack: &'static Stack>, id: u8, port: u16) } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index 6c4a78361..d66a43a88 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -38,8 +38,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -74,17 +74,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), - RESOURCES.init(StackResources::<2>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -119,7 +118,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 30a3a7463..97d9bd4c9 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -37,8 +37,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -73,17 +73,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), - RESOURCES.init(StackResources::<2>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -128,7 +127,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index 1613ed887..b1b5f9758 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -36,8 +36,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -71,17 +71,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), - RESOURCES.init(StackResources::<2>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); info!("Waiting for DHCP..."); let cfg = wait_for_config(stack).await; @@ -108,7 +107,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config.clone(); diff --git a/examples/rp/src/bin/orchestrate_tasks.rs b/examples/rp/src/bin/orchestrate_tasks.rs new file mode 100644 index 000000000..0e21d5833 --- /dev/null +++ b/examples/rp/src/bin/orchestrate_tasks.rs @@ -0,0 +1,318 @@ +//! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system. +//! +//! We demonstrate how to: +//! - use a channel to send messages between tasks, in this case here in order to have one task control the state of the system. +//! - use a signal to terminate a task. +//! - use command channels to send commands to another task. +//! - use different ways to receive messages, from a straightforwar awaiting on one channel to a more complex awaiting on multiple futures. +//! +//! There are more patterns to orchestrate tasks, this is just one example. +//! +//! We will use these tasks to generate example "state information": +//! - a task that generates random numbers in intervals of 60s +//! - a task that generates random numbers in intervals of 30s +//! - a task that generates random numbers in intervals of 90s +//! - a task that notifies about being attached/disattached from usb power +//! - a task that measures vsys voltage in intervals of 30s +//! - a task that consumes the state information and reacts to it + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::select::{select, Either}; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::{channel, signal}; +use embassy_time::{Duration, Timer}; +use rand::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +// This is just some preparation, see example `assign_resources.rs` for more information on this. We prep the rresources that we will be using in different tasks. +// **Note**: This will not work with a board that has a wifi chip, because the wifi chip uses pins 24 and 29 for its own purposes. A way around this in software +// is not trivial, at least if you intend to use wifi, too. Workaround is to wire from vsys and vbus pins to appropriate pins on the board through a voltage divider. Then use those pins. +// For this example it will not matter much, the concept of what we are showing remains valid. +assign_resources! { + vsys: Vsys { + adc: ADC, + pin_29: PIN_29, + }, + vbus: Vbus { + pin_24: PIN_24, + }, +} + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +/// This is the type of Events that we will send from the worker tasks to the orchestrating task. +enum Events { + UsbPowered(bool), + VsysVoltage(f32), + FirstRandomSeed(u32), + SecondRandomSeed(u32), + ThirdRandomSeed(u32), + ResetFirstRandomSeed, +} + +/// This is the type of Commands that we will send from the orchestrating task to the worker tasks. +/// Note that we are lazy here and only have one command, you might want to have more. +enum Commands { + /// This command will stop the appropriate worker task + Stop, +} + +/// This is the state of the system, we will use this to orchestrate the system. This is a simple example, in a real world application this would be more complex. +#[derive(Default, Debug, Clone, Format)] +struct State { + usb_powered: bool, + vsys_voltage: f32, + first_random_seed: u32, + second_random_seed: u32, + third_random_seed: u32, + times_we_got_first_random_seed: u8, + maximum_times_we_want_first_random_seed: u8, +} + +impl State { + fn new() -> Self { + Self { + usb_powered: false, + vsys_voltage: 0.0, + first_random_seed: 0, + second_random_seed: 0, + third_random_seed: 0, + times_we_got_first_random_seed: 0, + maximum_times_we_want_first_random_seed: 3, + } + } +} + +/// Channel for the events that we want the orchestrator to react to, all state events are of the type Enum Events. +/// We use a channel with an arbitrary size of 10, the precise size of the queue depends on your use case. This depends on how many events we +/// expect to be generated in a given time frame and how fast the orchestrator can react to them. And then if we rather want the senders to wait for +/// new slots in the queue or if we want the orchestrator to have a backlog of events to process. In this case here we expect to always be enough slots +/// in the queue, so the worker tasks can in all nominal cases send their events and continue with their work without waiting. +/// For the events we - in this case here - do not want to loose any events, so a channel is a good choice. See embassy_sync docs for other options. +static EVENT_CHANNEL: channel::Channel = channel::Channel::new(); + +/// Signal for stopping the first random signal task. We use a signal here, because we need no queue. It is suffiient to have one signal active. +static STOP_FIRST_RANDOM_SIGNAL: signal::Signal = signal::Signal::new(); + +/// Channel for the state that we want the consumer task to react to. We use a channel here, because we want to have a queue of state changes, although +/// we want the queue to be of size 1, because we want to finish rwacting to the state change before the next one comes in. This is just a design choice +/// and depends on your use case. +static CONSUMER_CHANNEL: channel::Channel = channel::Channel::new(); + +// And now we can put all this into use + +/// This is the main task, that will not do very much besides spawning the other tasks. This is a design choice, you could do the +/// orchestrating here. This is to show that we do not need a main loop here, the system will run indefinitely as long as at least one task is running. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // initialize the peripherals + let p = embassy_rp::init(Default::default()); + // split the resources, for convenience - see above + let r = split_resources! {p}; + + // spawn the tasks + spawner.spawn(orchestrate(spawner)).unwrap(); + spawner.spawn(random_60s(spawner)).unwrap(); + spawner.spawn(random_90s(spawner)).unwrap(); + spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); + spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); + spawner.spawn(consumer(spawner)).unwrap(); +} + +/// This is the task handling the system state and orchestrating the other tasks. WEe can regard this as the "main loop" of the system. +#[embassy_executor::task] +async fn orchestrate(_spawner: Spawner) { + let mut state = State::new(); + + // we need to have a receiver for the events + let receiver = EVENT_CHANNEL.receiver(); + + // and we need a sender for the consumer task + let state_sender = CONSUMER_CHANNEL.sender(); + + loop { + // we await on the receiver, this will block until a new event is available + // as an alternative to this, we could also await on multiple channels, this would block until at least one of the channels has an event + // see the embassy_futures docs: https://docs.embassy.dev/embassy-futures/git/default/select/index.html + // The task random_30s does a select, if you want to have a look at that. + // Another reason to use select may also be that we want to have a timeout, so we can react to the absence of events within a time frame. + // We keep it simple here. + let event = receiver.receive().await; + + // react to the events + match event { + Events::UsbPowered(usb_powered) => { + // update the state and/or react to the event here + state.usb_powered = usb_powered; + info!("Usb powered: {}", usb_powered); + } + Events::VsysVoltage(voltage) => { + // update the state and/or react to the event here + state.vsys_voltage = voltage; + info!("Vsys voltage: {}", voltage); + } + Events::FirstRandomSeed(seed) => { + // update the state and/or react to the event here + state.first_random_seed = seed; + // here we change some meta state, we count how many times we got the first random seed + state.times_we_got_first_random_seed += 1; + info!( + "First random seed: {}, and that was iteration {} of receiving this.", + seed, &state.times_we_got_first_random_seed + ); + } + Events::SecondRandomSeed(seed) => { + // update the state and/or react to the event here + state.second_random_seed = seed; + info!("Second random seed: {}", seed); + } + Events::ThirdRandomSeed(seed) => { + // update the state and/or react to the event here + state.third_random_seed = seed; + info!("Third random seed: {}", seed); + } + Events::ResetFirstRandomSeed => { + // update the state and/or react to the event here + state.times_we_got_first_random_seed = 0; + state.first_random_seed = 0; + info!("Resetting the first random seed counter"); + } + } + // we now have an altered state + // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here + // for now we just keep it simple + + // we send the state to the consumer task + // since the channel has a size of 1, this will block until the consumer task has received the state, which is what we want here in this example + // **Note:** It is bad design to send too much data between tasks, with no clear definition of what "too much" is. In this example we send the + // whole state, in a real world application you might want to send only the data, that is relevant to the consumer task AND only when it has changed. + // We keep it simple here. + state_sender.send(state.clone()).await; + } +} + +/// This task will consume the state information and react to it. This is a simple example, in a real world application this would be more complex +/// and we could have multiple consumer tasks, each reacting to different parts of the state. +#[embassy_executor::task] +async fn consumer(spawner: Spawner) { + // we need to have a receiver for the state + let receiver = CONSUMER_CHANNEL.receiver(); + let sender = EVENT_CHANNEL.sender(); + loop { + // we await on the receiver, this will block until a new state is available + let state = receiver.receive().await; + // react to the state, in this case here we just log it + info!("The consumer has reveived this state: {:?}", &state); + + // here we react to the state, in this case here we want to start or stop the first random signal task depending on the state of the system + match state.times_we_got_first_random_seed { + max if max == state.maximum_times_we_want_first_random_seed => { + info!("Stopping the first random signal task"); + // we send a command to the task + STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); + // we notify the orchestrator that we have sent the command + sender.send(Events::ResetFirstRandomSeed).await; + } + 0 => { + // we start the task, which presents us with an interesting problem, because we may return here before the task has started + // here we just try and log if the task has started, in a real world application you might want to handle this more gracefully + info!("Starting the first random signal task"); + match spawner.spawn(random_30s(spawner)) { + Ok(_) => info!("Successfully spawned random_30s task"), + Err(e) => info!("Failed to spawn random_30s task: {:?}", e), + } + } + _ => {} + } + } +} + +/// This task will generate random numbers in intervals of 30s +/// The task will terminate after it has received a command signal to stop, see the orchestrate task for that. +/// Note that we are not spawning this task from main, as we will show how such a task can be spawned and closed dynamically. +#[embassy_executor::task] +async fn random_30s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + loop { + // we either await on the timer or the signal, whichever comes first. + let futures = select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await; + match futures { + Either::First(_) => { + // we received are operating on the timer + info!("30s are up, generating random number"); + let random_number = rng.next_u32(); + sender.send(Events::FirstRandomSeed(random_number)).await; + } + Either::Second(_) => { + // we received the signal to stop + info!("Received signal to stop, goodbye!"); + break; + } + } + } +} + +/// This task will generate random numbers in intervals of 60s +#[embassy_executor::task] +async fn random_60s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + loop { + Timer::after(Duration::from_secs(60)).await; + let random_number = rng.next_u32(); + sender.send(Events::SecondRandomSeed(random_number)).await; + } +} + +/// This task will generate random numbers in intervals of 90s +#[embassy_executor::task] +async fn random_90s(_spawner: Spawner) { + let mut rng = RoscRng; + let sender = EVENT_CHANNEL.sender(); + loop { + Timer::after(Duration::from_secs(90)).await; + let random_number = rng.next_u32(); + sender.send(Events::ThirdRandomSeed(random_number)).await; + } +} + +/// This task will notify if we are connected to usb power +#[embassy_executor::task] +pub async fn usb_power(_spawner: Spawner, r: Vbus) { + let mut vbus_in = Input::new(r.pin_24, Pull::None); + let sender = EVENT_CHANNEL.sender(); + loop { + sender.send(Events::UsbPowered(vbus_in.is_high())).await; + vbus_in.wait_for_any_edge().await; + } +} + +/// This task will measure the vsys voltage in intervals of 30s +#[embassy_executor::task] +pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) { + let mut adc = Adc::new(r.adc, Irqs, Config::default()); + let vsys_in = r.pin_29; + let mut channel = Channel::new_pin(vsys_in, Pull::None); + let sender = EVENT_CHANNEL.sender(); + loop { + // read the adc value + let adc_value = adc.read(&mut channel).await.unwrap(); + // convert the adc value to voltage. + // 3.3 is the reference voltage, 3.0 is the factor for the inbuilt voltage divider and 4096 is the resolution of the adc + let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0; + sender.send(Events::VsysVoltage(voltage)).await; + Timer::after(Duration::from_secs(30)).await; + } +} diff --git a/examples/rp/src/bin/pio_onewire.rs b/examples/rp/src/bin/pio_onewire.rs new file mode 100644 index 000000000..5076101ec --- /dev/null +++ b/examples/rp/src/bin/pio_onewire.rs @@ -0,0 +1,155 @@ +//! This example shows how you can use PIO to read a `DS18B20` one-wire temperature sensor. + +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{self, Common, Config, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut pio = Pio::new(p.PIO0, Irqs); + let mut sensor = Ds18b20::new(&mut pio.common, pio.sm0, p.PIN_2); + + loop { + sensor.start().await; // Start a new measurement + Timer::after_secs(1).await; // Allow 1s for the measurement to finish + match sensor.temperature().await { + Ok(temp) => info!("temp = {:?} deg C", temp), + _ => error!("sensor error"), + } + Timer::after_secs(1).await; + } +} + +/// DS18B20 temperature sensor driver +pub struct Ds18b20<'d, PIO: pio::Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: pio::Instance, const SM: usize> Ds18b20<'d, PIO, SM> { + /// Create a new instance the driver + pub fn new(common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, pin: impl PioPin) -> Self { + let prg = pio_proc::pio_asm!( + r#" + .wrap_target + again: + pull block + mov x, osr + jmp !x, read + write: + set pindirs, 1 + set pins, 0 + loop1: + jmp x--,loop1 + set pindirs, 0 [31] + wait 1 pin 0 [31] + pull block + mov x, osr + bytes1: + pull block + set y, 7 + set pindirs, 1 + bit1: + set pins, 0 [1] + out pins,1 [31] + set pins, 1 [20] + jmp y--,bit1 + jmp x--,bytes1 + set pindirs, 0 [31] + jmp again + read: + pull block + mov x, osr + bytes2: + set y, 7 + bit2: + set pindirs, 1 + set pins, 0 [1] + set pindirs, 0 [5] + in pins,1 [10] + jmp y--,bit2 + jmp x--,bytes2 + .wrap + "#, + ); + + let pin = common.make_pio_pin(pin); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[]); + cfg.set_out_pins(&[&pin]); + cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); + cfg.shift_in = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.clock_divider = 255_u8.into(); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Write bytes over the wire + async fn write_bytes(&mut self, bytes: &[u8]) { + self.sm.tx().wait_push(250).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes { + self.sm.tx().wait_push(*b as u32).await; + } + } + + /// Read bytes from the wire + async fn read_bytes(&mut self, bytes: &mut [u8]) { + self.sm.tx().wait_push(0).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes.iter_mut() { + *b = (self.sm.rx().wait_pull().await >> 24) as u8; + } + } + + /// Calculate CRC8 of the data + fn crc8(data: &[u8]) -> u8 { + let mut temp; + let mut data_byte; + let mut crc = 0; + for b in data { + data_byte = *b; + for _ in 0..8 { + temp = (crc ^ data_byte) & 0x01; + crc >>= 1; + if temp != 0 { + crc ^= 0x8C; + } + data_byte >>= 1; + } + } + crc + } + + /// Start a new measurement. Allow at least 1000ms before getting `temperature`. + pub async fn start(&mut self) { + self.write_bytes(&[0xCC, 0x44]).await; + } + + /// Read the temperature. Ensure >1000ms has passed since `start` before calling this. + pub async fn temperature(&mut self) -> Result { + self.write_bytes(&[0xCC, 0xBE]).await; + let mut data = [0; 9]; + self.read_bytes(&mut data).await; + match Self::crc8(&data) == 0 { + true => Ok(((data[1] as u32) << 8 | data[0] as u32) as f32 / 16.), + false => Err(()), + } + } +} diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 22dc88d28..9a15125d4 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -8,7 +8,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_rp::clocks::RoscRng; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, InterruptHandler}; @@ -40,8 +40,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -108,16 +108,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/rp/src/bin/usb_hid_mouse.rs b/examples/rp/src/bin/usb_hid_mouse.rs index cce344fb0..5ee650910 100644 --- a/examples/rp/src/bin/usb_hid_mouse.rs +++ b/examples/rp/src/bin/usb_hid_mouse.rs @@ -8,7 +8,6 @@ use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; -use embassy_rp::gpio::{Input, Pull}; use embassy_rp::peripherals::USB; use embassy_rp::usb::{Driver, InterruptHandler}; use embassy_time::Timer; @@ -75,12 +74,6 @@ async fn main(_spawner: Spawner) { // Run the USB device. let usb_fut = usb.run(); - // Set up the signal pin that will be used to trigger the keyboard. - let mut signal_pin = Input::new(p.PIN_16, Pull::None); - - // Enable the schmitt trigger to slightly debounce. - signal_pin.set_schmitt(true); - let (reader, mut writer) = hid.split(); // Do stuff with the class! diff --git a/examples/rp/src/bin/wifi_ap_tcp_server.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs index 4fc2690e3..4c9651433 100644 --- a/examples/rp/src/bin/wifi_ap_tcp_server.rs +++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs @@ -11,7 +11,7 @@ use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -28,13 +28,13 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -62,7 +62,7 @@ async fn main(spawner: Spawner) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; - unwrap!(spawner.spawn(wifi_task(runner))); + unwrap!(spawner.spawn(cyw43_task(runner))); control.init(clm).await; control @@ -80,16 +80,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); //control.start_ap_open("cyw43", 5).await; control.start_ap_wpa2("cyw43", "password", 5).await; diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs index 471349639..04a61bbd5 100644 --- a/examples/rp/src/bin/wifi_blinky.rs +++ b/examples/rp/src/bin/wifi_blinky.rs @@ -21,7 +21,7 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } @@ -46,7 +46,7 @@ async fn main(spawner: Spawner) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; - unwrap!(spawner.spawn(wifi_task(runner))); + unwrap!(spawner.spawn(cyw43_task(runner))); control.init(clm).await; control diff --git a/examples/rp/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs index 5f4c848a2..434f0074c 100644 --- a/examples/rp/src/bin/wifi_scan.rs +++ b/examples/rp/src/bin/wifi_scan.rs @@ -10,7 +10,6 @@ use core::str; use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; -use embassy_net::Stack; use embassy_rp::bind_interrupts; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIO0}; @@ -23,13 +22,13 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -56,7 +55,7 @@ async fn main(spawner: Spawner) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; - unwrap!(spawner.spawn(wifi_task(runner))); + unwrap!(spawner.spawn(cyw43_task(runner))); control.init(clm).await; control diff --git a/examples/rp/src/bin/wifi_tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs index 5575df677..7bf546e01 100644 --- a/examples/rp/src/bin/wifi_tcp_server.rs +++ b/examples/rp/src/bin/wifi_tcp_server.rs @@ -7,11 +7,12 @@ use core::str::from_utf8; +use cyw43::JoinOptions; use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -27,17 +28,17 @@ bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; }); -const WIFI_NETWORK: &str = "EmbassyTest"; -const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; +const WIFI_NETWORK: &str = "LadronDeWifi"; +const WIFI_PASSWORD: &str = "MBfcaedHmyRFE4kaQ1O5SsY8"; #[embassy_executor::task] -async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -65,7 +66,7 @@ async fn main(spawner: Spawner) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; - unwrap!(spawner.spawn(wifi_task(runner))); + unwrap!(spawner.spawn(cyw43_task(runner))); control.init(clm).await; control @@ -83,20 +84,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { - //control.join_open(WIFI_NETWORK).await; - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); diff --git a/examples/rp/src/bin/wifi_webrequest.rs b/examples/rp/src/bin/wifi_webrequest.rs index 70b6f0949..1ae909917 100644 --- a/examples/rp/src/bin/wifi_webrequest.rs +++ b/examples/rp/src/bin/wifi_webrequest.rs @@ -7,12 +7,13 @@ use core::str::from_utf8; +use cyw43::JoinOptions; use cyw43_pio::PioSpi; use defmt::*; use embassy_executor::Spawner; use embassy_net::dns::DnsSocket; use embassy_net::tcp::client::{TcpClient, TcpClientState}; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::bind_interrupts; use embassy_rp::clocks::RoscRng; use embassy_rp::gpio::{Level, Output}; @@ -34,13 +35,13 @@ const WIFI_NETWORK: &str = "ssid"; // change to your network SSID const WIFI_PASSWORD: &str = "pwd"; // change to your network password #[embassy_executor::task] -async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { +async fn cyw43_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -67,7 +68,7 @@ async fn main(spawner: Spawner) { static STATE: StaticCell = StaticCell::new(); let state = STATE.init(cyw43::State::new()); let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; - unwrap!(spawner.spawn(wifi_task(runner))); + unwrap!(spawner.spawn(cyw43_task(runner))); control.init(clm).await; control @@ -86,20 +87,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - net_device, - config, - RESOURCES.init(StackResources::<5>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(net_device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { - //match control.join_open(WIFI_NETWORK).await { // for open networks - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { info!("join failed with status={}", err.status); diff --git a/examples/rp23/.cargo/config.toml b/examples/rp23/.cargo/config.toml new file mode 100644 index 000000000..9a92b1ce2 --- /dev/null +++ b/examples/rp23/.cargo/config.toml @@ -0,0 +1,10 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "probe-rs run --chip RP2040" +#runner = "elf2uf2-rs -d" +runner = "picotool load -u -v -x -t elf" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/examples/rp23/Cargo.toml b/examples/rp23/Cargo.toml new file mode 100644 index 000000000..087f6fd69 --- /dev/null +++ b/examples/rp23/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2021" +name = "embassy-rp2350-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + + +[dependencies] +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-98304", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp235xa", "binary-info"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "raw", "dhcpv4", "medium-ethernet", "dns"] } +embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.2.0", path = "../../embassy-usb-logger" } +cyw43 = { version = "0.2.0", path = "../../cyw43", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.2.0", path = "../../cyw43-pio", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" + +# for web request example +reqwless = { version = "0.12.0", features = ["defmt",]} +serde = { version = "1.0.203", default-features = false, features = ["derive"] } +serde-json-core = "0.5.1" + +# for assign resources example +assign-resources = { git = "https://github.com/adamgreig/assign-resources", rev = "94ad10e2729afdf0fd5a77cd12e68409a982f58a" } + +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } +cortex-m-rt = "0.7.0" +critical-section = "1.1" +panic-probe = { version = "0.3", features = ["print-defmt"] } +display-interface-spi = "0.4.1" +embedded-graphics = "0.7.1" +st7789 = "0.6.1" +display-interface = "0.4.1" +byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.3.0" +heapless = "0.8" +usbd-hid = "0.8.1" + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = "1.0" +embedded-hal-bus = { version = "0.1", features = ["async"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-storage = { version = "0.3" } +static_cell = "2.1" +portable-atomic = { version = "1.5", features = ["critical-section"] } +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } +embedded-sdmmc = "0.7.0" + +bt-hci = { version = "0.1.0", default-features = false, features = ["defmt"] } +trouble-host = { version = "0.1.0", features = ["defmt", "gatt"] } + +[profile.release] +debug = 2 + +[profile.dev] +lto = true +opt-level = "z" + +[patch.crates-io] +trouble-host = { git = "https://github.com/embassy-rs/trouble.git", rev = "4b8c0f499b34e46ca23a56e2d1640ede371722cf" } +embassy-executor = { path = "../../embassy-executor" } +embassy-sync = { path = "../../embassy-sync" } +embassy-futures = { path = "../../embassy-futures" } +embassy-time = { path = "../../embassy-time" } +embassy-time-driver = { path = "../../embassy-time-driver" } +embassy-embedded-hal = { path = "../../embassy-embedded-hal" } diff --git a/examples/rp23/assets/ferris.raw b/examples/rp23/assets/ferris.raw new file mode 100644 index 000000000..9733889c5 Binary files /dev/null and b/examples/rp23/assets/ferris.raw differ diff --git a/examples/rp23/build.rs b/examples/rp23/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/rp23/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/rp23/memory.x b/examples/rp23/memory.x new file mode 100644 index 000000000..777492062 --- /dev/null +++ b/examples/rp23/memory.x @@ -0,0 +1,74 @@ +MEMORY { + /* + * The RP2350 has either external or internal flash. + * + * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. + */ + FLASH : ORIGIN = 0x10000000, LENGTH = 2048K + /* + * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. + * This is usually good for performance, as it distributes load on + * those banks evenly. + */ + RAM : ORIGIN = 0x20000000, LENGTH = 512K + /* + * RAM banks 8 and 9 use a direct mapping. They can be used to have + * memory areas dedicated for some specific job, improving predictability + * of access times. + * Example: Separate stacks for core0 and core1. + */ + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K + SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K +} + +SECTIONS { + /* ### Boot ROM info + * + * Goes after .vector_table, to keep it in the first 4K of flash + * where the Boot ROM (and picotool) can find it + */ + .start_block : ALIGN(4) + { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH + +} INSERT AFTER .vector_table; + +/* move .text to start /after/ the boot info */ +_stext = ADDR(.start_block) + SIZEOF(.start_block); + +SECTIONS { + /* ### Picotool 'Binary Info' Entries + * + * Picotool looks through this block (as we have pointers to it in our + * header) to find interesting information. + */ + .bi_entries : ALIGN(4) + { + /* We put this in the header */ + __bi_entries_start = .; + /* Here are the entries */ + KEEP(*(.bi_entries)); + /* Keep this block a nice round size */ + . = ALIGN(4); + /* We put this in the header */ + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; + +SECTIONS { + /* ### Boot ROM extra info + * + * Goes after everything in our program, so it can contain a signature. + */ + .end_block : ALIGN(4) + { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH + +} INSERT AFTER .uninit; + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/examples/rp23/src/bin/adc.rs b/examples/rp23/src/bin/adc.rs new file mode 100644 index 000000000..d1f053d39 --- /dev/null +++ b/examples/rp23/src/bin/adc.rs @@ -0,0 +1,63 @@ +//! This example test the ADC (Analog to Digital Conversion) of the RS2040 pin 26, 27 and 28. +//! It also reads the temperature sensor in the chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + + let mut p26 = Channel::new_pin(p.PIN_26, Pull::None); + let mut p27 = Channel::new_pin(p.PIN_27, Pull::None); + let mut p28 = Channel::new_pin(p.PIN_28, Pull::None); + let mut ts = Channel::new_temp_sensor(p.ADC_TEMP_SENSOR); + + loop { + let level = adc.read(&mut p26).await.unwrap(); + info!("Pin 26 ADC: {}", level); + let level = adc.read(&mut p27).await.unwrap(); + info!("Pin 27 ADC: {}", level); + let level = adc.read(&mut p28).await.unwrap(); + info!("Pin 28 ADC: {}", level); + let temp = adc.read(&mut ts).await.unwrap(); + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after_secs(1).await; + } +} + +fn convert_to_celsius(raw_temp: u16) -> f32 { + // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721; + let sign = if temp < 0.0 { -1.0 } else { 1.0 }; + let rounded_temp_x10: i16 = ((temp * 10.0) + 0.5 * sign) as i16; + (rounded_temp_x10 as f32) / 10.0 +} diff --git a/examples/rp23/src/bin/adc_dma.rs b/examples/rp23/src/bin/adc_dma.rs new file mode 100644 index 000000000..5046e5530 --- /dev/null +++ b/examples/rp23/src/bin/adc_dma.rs @@ -0,0 +1,69 @@ +//! This example shows how to use the RP2040 ADC with DMA, both single- and multichannel reads. +//! For multichannel, the samples are interleaved in the buffer: +//! `[ch1, ch2, ch3, ch4, ch1, ch2, ch3, ch4, ...]` +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_time::{Duration, Ticker}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + let mut dma = p.DMA_CH0; + let mut pin = Channel::new_pin(p.PIN_26, Pull::Up); + let mut pins = [ + Channel::new_pin(p.PIN_27, Pull::Down), + Channel::new_pin(p.PIN_28, Pull::None), + Channel::new_pin(p.PIN_29, Pull::Up), + Channel::new_temp_sensor(p.ADC_TEMP_SENSOR), + ]; + + const BLOCK_SIZE: usize = 100; + const NUM_CHANNELS: usize = 4; + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + // Read 100 samples from a single channel + let mut buf = [0_u16; BLOCK_SIZE]; + let div = 479; // 100kHz sample rate (48Mhz / 100kHz - 1) + adc.read_many(&mut pin, &mut buf, div, &mut dma).await.unwrap(); + info!("single: {:?} ...etc", buf[..8]); + + // Read 100 samples from 4 channels interleaved + let mut buf = [0_u16; { BLOCK_SIZE * NUM_CHANNELS }]; + let div = 119; // 100kHz sample rate (48Mhz / 100kHz * 4ch - 1) + adc.read_many_multichannel(&mut pins, &mut buf, div, &mut dma) + .await + .unwrap(); + info!("multi: {:?} ...etc", buf[..NUM_CHANNELS * 2]); + + ticker.next().await; + } +} diff --git a/examples/rp23/src/bin/assign_resources.rs b/examples/rp23/src/bin/assign_resources.rs new file mode 100644 index 000000000..2f9783917 --- /dev/null +++ b/examples/rp23/src/bin/assign_resources.rs @@ -0,0 +1,94 @@ +//! This example demonstrates how to assign resources to multiple tasks by splitting up the peripherals. +//! It is not about sharing the same resources between tasks, see sharing.rs for that or head to https://embassy.dev/book/#_sharing_peripherals_between_tasks) +//! Of course splitting up resources and sharing resources can be combined, yet this example is only about splitting up resources. +//! +//! There are basically two ways we demonstrate here: +//! 1) Assigning resources to a task by passing parts of the peripherals +//! 2) Assigning resources to a task by passing a struct with the split up peripherals, using the assign-resources macro +//! +//! using four LEDs on Pins 10, 11, 20 and 21 + +#![no_std] +#![no_main] + +use assign_resources::assign_resources; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{self, PIN_20, PIN_21}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // initialize the peripherals + let p = embassy_rp::init(Default::default()); + + // 1) Assigning a resource to a task by passing parts of the peripherals. + spawner + .spawn(double_blinky_manually_assigned(spawner, p.PIN_20, p.PIN_21)) + .unwrap(); + + // 2) Using the assign-resources macro to assign resources to a task. + // we perform the split, see further below for the definition of the resources struct + let r = split_resources!(p); + // and then we can use them + spawner.spawn(double_blinky_macro_assigned(spawner, r.leds)).unwrap(); +} + +// 1) Assigning a resource to a task by passing parts of the peripherals. +#[embassy_executor::task] +async fn double_blinky_manually_assigned(_spawner: Spawner, pin_20: PIN_20, pin_21: PIN_21) { + let mut led_20 = Output::new(pin_20, Level::Low); + let mut led_21 = Output::new(pin_21, Level::High); + + loop { + info!("toggling leds"); + led_20.toggle(); + led_21.toggle(); + Timer::after_secs(1).await; + } +} + +// 2) Using the assign-resources macro to assign resources to a task. +// first we define the resources we want to assign to the task using the assign_resources! macro +// basically this will split up the peripherals struct into smaller structs, that we define here +// naming is up to you, make sure your future self understands what you did here +assign_resources! { + leds: Leds{ + led_10: PIN_10, + led_11: PIN_11, + } + // add more resources to more structs if needed, for example defining one struct for each task +} +// this could be done in another file and imported here, but for the sake of simplicity we do it here +// see https://github.com/adamgreig/assign-resources for more information + +// 2) Using the split resources in a task +#[embassy_executor::task] +async fn double_blinky_macro_assigned(_spawner: Spawner, r: Leds) { + let mut led_10 = Output::new(r.led_10, Level::Low); + let mut led_11 = Output::new(r.led_11, Level::High); + + loop { + info!("toggling leds"); + led_10.toggle(); + led_11.toggle(); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/blinky.rs b/examples/rp23/src/bin/blinky.rs new file mode 100644 index 000000000..9e45679c8 --- /dev/null +++ b/examples/rp23/src/bin/blinky.rs @@ -0,0 +1,44 @@ +//! This example test the RP Pico on board LED. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_time::Timer; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_2, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after_millis(250).await; + + info!("led off!"); + led.set_low(); + Timer::after_millis(250).await; + } +} diff --git a/examples/rp23/src/bin/blinky_two_channels.rs b/examples/rp23/src/bin/blinky_two_channels.rs new file mode 100644 index 000000000..87fc58bbc --- /dev/null +++ b/examples/rp23/src/bin/blinky_two_channels.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] +/// This example demonstrates how to access a given pin from more than one embassy task +/// The on-board LED is toggled by two tasks with slightly different periods, leading to the +/// apparent duty cycle of the LED increasing, then decreasing, linearly. The phenomenon is similar +/// to interference and the 'beats' you can hear if you play two frequencies close to one another +/// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::{Channel, Sender}; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +enum LedState { + Toggle, +} +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High); + + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led( + CHANNEL.sender(), + Duration::from_nanos((dt as f64 * k) as u64) + ))); + + loop { + match CHANNEL.receive().await { + LedState::Toggle => led.toggle(), + } + } +} + +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + control.send(LedState::Toggle).await; + ticker.next().await; + } +} diff --git a/examples/rp23/src/bin/blinky_two_tasks.rs b/examples/rp23/src/bin/blinky_two_tasks.rs new file mode 100644 index 000000000..40236c53b --- /dev/null +++ b/examples/rp23/src/bin/blinky_two_tasks.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] +/// This example demonstrates how to access a given pin from more than one embassy task +/// The on-board LED is toggled by two tasks with slightly different periods, leading to the +/// apparent duty cycle of the LED increasing, then decreasing, linearly. The phenomenon is similar +/// to interference and the 'beats' you can hear if you play two frequencies close to one another +/// [Link explaining it](https://www.physicsclassroom.com/class/sound/Lesson-3/Interference-and-Beats) +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::{Duration, Ticker}; +use gpio::{AnyPin, Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +type LedType = Mutex>>; +static LED: LedType = Mutex::new(None); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // set the content of the global LED reference to the real LED pin + let led = Output::new(AnyPin::from(p.PIN_25), Level::High); + // inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the + // Mutex is released + { + *(LED.lock().await) = Some(led); + } + let dt = 100 * 1_000_000; + let k = 1.003; + + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt)))); + unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64)))); +} + +#[embassy_executor::task(pool_size = 2)] +async fn toggle_led(led: &'static LedType, delay: Duration) { + let mut ticker = Ticker::every(delay); + loop { + { + let mut led_unlocked = led.lock().await; + if let Some(pin_ref) = led_unlocked.as_mut() { + pin_ref.toggle(); + } + } + ticker.next().await; + } +} diff --git a/examples/rp23/src/bin/button.rs b/examples/rp23/src/bin/button.rs new file mode 100644 index 000000000..fb067a370 --- /dev/null +++ b/examples/rp23/src/bin/button.rs @@ -0,0 +1,43 @@ +//! This example uses the RP Pico on board LED to test input pin 28. This is not the button on the board. +//! +//! It does not work with the RP Pico W board. Use wifi_blinky.rs and add input pin. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Use PIN_28, Pin34 on J0 for RP Pico, as a input. + // You need to add your own button. + let button = Input::new(p.PIN_28, Pull::Up); + + loop { + if button.is_high() { + led.set_high(); + } else { + led.set_low(); + } + } +} diff --git a/examples/rp23/src/bin/debounce.rs b/examples/rp23/src/bin/debounce.rs new file mode 100644 index 000000000..e672521ec --- /dev/null +++ b/examples/rp23/src/bin/debounce.rs @@ -0,0 +1,95 @@ +//! This example shows the ease of debouncing a button with async rust. +//! Hook up a button or switch between pin 9 and ground. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Input, Level, Pull}; +use embassy_time::{with_deadline, Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +pub struct Debouncer<'a> { + input: Input<'a>, + debounce: Duration, +} + +impl<'a> Debouncer<'a> { + pub fn new(input: Input<'a>, debounce: Duration) -> Self { + Self { input, debounce } + } + + pub async fn debounce(&mut self) -> Level { + loop { + let l1 = self.input.get_level(); + + self.input.wait_for_any_edge().await; + + Timer::after(self.debounce).await; + + let l2 = self.input.get_level(); + if l1 != l2 { + break l2; + } + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut btn = Debouncer::new(Input::new(p.PIN_9, Pull::Up), Duration::from_millis(20)); + + info!("Debounce Demo"); + + loop { + // button pressed + btn.debounce().await; + let start = Instant::now(); + info!("Button Press"); + + match with_deadline(start + Duration::from_secs(1), btn.debounce()).await { + // Button Released < 1s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > 1s + Err(_) => { + info!("Button Held"); + } + } + + match with_deadline(start + Duration::from_secs(5), btn.debounce()).await { + // Button released <5s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > >5s + Err(_) => { + info!("Button Long Held"); + } + } + + // wait for button release before handling another press + btn.debounce().await; + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + } +} diff --git a/examples/rp23/src/bin/flash.rs b/examples/rp23/src/bin/flash.rs new file mode 100644 index 000000000..84011e394 --- /dev/null +++ b/examples/rp23/src/bin/flash.rs @@ -0,0 +1,140 @@ +//! This example test the flash connected to the RP2040 chip. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::flash::{Async, ERASE_SIZE, FLASH_BASE}; +use embassy_rp::peripherals::FLASH; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Flash"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +const ADDR_OFFSET: u32 = 0x100000; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let mut flash = embassy_rp::flash::Flash::<_, Async, FLASH_SIZE>::new(p.FLASH, p.DMA_CH0); + + erase_write_sector(&mut flash, 0x00); + + multiwrite_bytes(&mut flash, ERASE_SIZE as u32); + + background_read(&mut flash, (ERASE_SIZE * 2) as u32).await; + + info!("Flash Works!"); +} + +fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [multiwrite_bytes]"); + let mut read_buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", read_buf[0..4]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after erase starts with {=[u8]}", read_buf[0..4]); + if read_buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &[0x01])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 1, &[0x02])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 2, &[0x03])); + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset + 3, &[0x04])); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after write starts with {=[u8]}", read_buf[0..4]); + if read_buf[0..4] != [0x01, 0x02, 0x03, 0x04] { + defmt::panic!("unexpected"); + } +} + +fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [erase_write_sector]"); + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after erase starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDA; + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, &buf)); + + defmt::unwrap!(flash.blocking_read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after write starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xDA) { + defmt::panic!("unexpected"); + } +} + +async fn background_read(flash: &mut embassy_rp::flash::Flash<'_, FLASH, Async, FLASH_SIZE>, offset: u32) { + info!(">>>> [background_read]"); + + let mut buf = [0u32; 8]; + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=u32:x}", buf[0]); + + defmt::unwrap!(flash.blocking_erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + info!("Contents after erase starts with {=u32:x}", buf[0]); + if buf.iter().any(|x| *x != 0xFFFFFFFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDABA1234; + } + + defmt::unwrap!(flash.blocking_write(ADDR_OFFSET + offset, unsafe { + core::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len() * 4) + })); + + defmt::unwrap!(flash.background_read(ADDR_OFFSET + offset, &mut buf)).await; + info!("Contents after write starts with {=u32:x}", buf[0]); + if buf.iter().any(|x| *x != 0xDABA1234) { + defmt::panic!("unexpected"); + } +} diff --git a/examples/rp23/src/bin/gpio_async.rs b/examples/rp23/src/bin/gpio_async.rs new file mode 100644 index 000000000..ff12367bf --- /dev/null +++ b/examples/rp23/src/bin/gpio_async.rs @@ -0,0 +1,55 @@ +//! This example shows how async gpio can be used with a RP2040. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_time::Timer; +use gpio::{Input, Level, Output, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +/// It requires an external signal to be manually triggered on PIN 16. For +/// example, this could be accomplished using an external power source with a +/// button so that it is possible to toggle the signal from low to high. +/// +/// This example will begin with turning on the LED on the board and wait for a +/// high signal on PIN 16. Once the high event/signal occurs the program will +/// continue and turn off the LED, and then wait for 2 seconds before completing +/// the loop and starting over again. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + let mut async_input = Input::new(p.PIN_16, Pull::None); + + loop { + info!("wait_for_high. Turn on LED"); + led.set_high(); + + async_input.wait_for_high().await; + + info!("done wait_for_high. Turn off LED"); + led.set_low(); + + Timer::after_secs(2).await; + } +} diff --git a/examples/rp23/src/bin/gpout.rs b/examples/rp23/src/bin/gpout.rs new file mode 100644 index 000000000..d2ee55197 --- /dev/null +++ b/examples/rp23/src/bin/gpout.rs @@ -0,0 +1,52 @@ +//! This example shows how GPOUT (General purpose clock outputs) can toggle a output pin. +//! +//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::clocks; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let gpout3 = clocks::Gpout::new(p.PIN_25); + gpout3.set_div(1000, 0); + gpout3.enable(); + + loop { + gpout3.set_src(clocks::GpoutSrc::Sys); + info!( + "Pin 25 is now outputing CLK_SYS/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after_secs(2).await; + + gpout3.set_src(clocks::GpoutSrc::Ref); + info!( + "Pin 25 is now outputing CLK_REF/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after_secs(2).await; + } +} diff --git a/examples/rp23/src/bin/i2c_async.rs b/examples/rp23/src/bin/i2c_async.rs new file mode 100644 index 000000000..c8d918b56 --- /dev/null +++ b/examples/rp23/src/bin/i2c_async.rs @@ -0,0 +1,125 @@ +//! This example shows how to communicate asynchronous using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::i2c::{self, Config, InterruptHandler}; +use embassy_rp::peripherals::I2C1; +use embassy_time::Timer; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + macro_rules! mcpregs { + ($($name:ident : $val:expr),* $(,)?) => { + $( + pub const $name: u8 = $val; + )* + + pub fn regname(reg: u8) -> &'static str { + match reg { + $( + $val => stringify!($name), + )* + _ => panic!("bad reg"), + } + } + } + } + + // These are correct for IOCON.BANK=0 + mcpregs! { + IODIRA: 0x00, + IPOLA: 0x02, + GPINTENA: 0x04, + DEFVALA: 0x06, + INTCONA: 0x08, + IOCONA: 0x0A, + GPPUA: 0x0C, + INTFA: 0x0E, + INTCAPA: 0x10, + GPIOA: 0x12, + OLATA: 0x14, + IODIRB: 0x01, + IPOLB: 0x03, + GPINTENB: 0x05, + DEFVALB: 0x07, + INTCONB: 0x09, + IOCONB: 0x0B, + GPPUB: 0x0D, + INTFB: 0x0F, + INTCAPB: 0x11, + GPIOB: 0x13, + OLATB: 0x15, + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).await.unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).await.unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).await.unwrap(); // pullups + + let mut val = 1; + loop { + let mut portb = [0]; + + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).await.unwrap(); + info!("portb = {:02x}", portb[0]); + i2c.write(mcp23017::ADDR, &[GPIOA, val | portb[0]]).await.unwrap(); + val = val.rotate_left(1); + + // get a register dump + info!("getting register dump"); + let mut regs = [0; 22]; + i2c.write_read(ADDR, &[0], &mut regs).await.unwrap(); + // always get the regdump but only display it if portb'0 is set + if portb[0] & 1 != 0 { + for (idx, reg) in regs.into_iter().enumerate() { + info!("{} => {:02x}", regname(idx as u8), reg); + } + } + + Timer::after_millis(100).await; + } +} diff --git a/examples/rp23/src/bin/i2c_async_embassy.rs b/examples/rp23/src/bin/i2c_async_embassy.rs new file mode 100644 index 000000000..cce0abcde --- /dev/null +++ b/examples/rp23/src/bin/i2c_async_embassy.rs @@ -0,0 +1,100 @@ +//! This example shows how to communicate asynchronous using i2c with external chip. +//! +//! It's using embassy's functions directly instead of traits from embedded_hal_async::i2c::I2c. +//! While most of i2c devices are addressed using 7 bits, an extension allows 10 bits too. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_rp::block::ImageDef; +use embassy_rp::i2c::InterruptHandler; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +// Our anonymous hypotetical temperature sensor could be: +// a 12-bit sensor, with 100ms startup time, range of -40*C - 125*C, and precision 0.25*C +// It requires no configuration or calibration, works with all i2c bus speeds, +// never stretches clock or does anything complicated. Replies with one u16. +// It requires only one write to take it out of suspend mode, and stays on. +// Often result would be just on 12 bits, but here we'll simplify it to 16. + +enum UncomplicatedSensorId { + A(UncomplicatedSensorU8), + B(UncomplicatedSensorU16), +} +enum UncomplicatedSensorU8 { + First = 0x48, +} +enum UncomplicatedSensorU16 { + Other = 0x0049, +} + +impl Into for UncomplicatedSensorU16 { + fn into(self) -> u16 { + self as u16 + } +} +impl Into for UncomplicatedSensorU8 { + fn into(self) -> u16 { + 0x48 + } +} +impl From for u16 { + fn from(t: UncomplicatedSensorId) -> Self { + match t { + UncomplicatedSensorId::A(x) => x.into(), + UncomplicatedSensorId::B(x) => x.into(), + } + } +} + +embassy_rp::bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_task_spawner: embassy_executor::Spawner) { + let p = embassy_rp::init(Default::default()); + let sda = p.PIN_14; + let scl = p.PIN_15; + let config = embassy_rp::i2c::Config::default(); + let mut bus = embassy_rp::i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, config); + + const WAKEYWAKEY: u16 = 0xBABE; + let mut result: [u8; 2] = [0, 0]; + // wait for sensors to initialize + embassy_time::Timer::after(embassy_time::Duration::from_millis(100)).await; + + let _res_1 = bus + .write_async(UncomplicatedSensorU8::First, WAKEYWAKEY.to_be_bytes()) + .await; + let _res_2 = bus + .write_async(UncomplicatedSensorU16::Other, WAKEYWAKEY.to_be_bytes()) + .await; + + loop { + let s1 = UncomplicatedSensorId::A(UncomplicatedSensorU8::First); + let s2 = UncomplicatedSensorId::B(UncomplicatedSensorU16::Other); + let sensors = [s1, s2]; + for sensor in sensors { + if bus.read_async(sensor, &mut result).await.is_ok() { + info!("Result {}", u16::from_be_bytes(result.into())); + } + } + embassy_time::Timer::after(embassy_time::Duration::from_millis(200)).await; + } +} diff --git a/examples/rp23/src/bin/i2c_blocking.rs b/examples/rp23/src/bin/i2c_blocking.rs new file mode 100644 index 000000000..85c33bf0d --- /dev/null +++ b/examples/rp23/src/bin/i2c_blocking.rs @@ -0,0 +1,89 @@ +//! This example shows how to communicate using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::i2c::{self, Config}; +use embassy_time::Timer; +use embedded_hal_1::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + pub const IODIRA: u8 = 0x00; + pub const IPOLA: u8 = 0x02; + pub const GPINTENA: u8 = 0x04; + pub const DEFVALA: u8 = 0x06; + pub const INTCONA: u8 = 0x08; + pub const IOCONA: u8 = 0x0A; + pub const GPPUA: u8 = 0x0C; + pub const INTFA: u8 = 0x0E; + pub const INTCAPA: u8 = 0x10; + pub const GPIOA: u8 = 0x12; + pub const OLATA: u8 = 0x14; + pub const IODIRB: u8 = 0x01; + pub const IPOLB: u8 = 0x03; + pub const GPINTENB: u8 = 0x05; + pub const DEFVALB: u8 = 0x07; + pub const INTCONB: u8 = 0x09; + pub const IOCONB: u8 = 0x0B; + pub const GPPUB: u8 = 0x0D; + pub const INTFB: u8 = 0x0F; + pub const INTCAPB: u8 = 0x11; + pub const GPIOB: u8 = 0x13; + pub const OLATB: u8 = 0x15; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_blocking(p.I2C1, scl, sda, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).unwrap(); // pullups + + let mut val = 0xaa; + loop { + let mut portb = [0]; + + i2c.write(mcp23017::ADDR, &[GPIOA, val]).unwrap(); + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).unwrap(); + + info!("portb = {:02x}", portb[0]); + val = !val; + + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/i2c_slave.rs b/examples/rp23/src/bin/i2c_slave.rs new file mode 100644 index 000000000..fb5f3cda1 --- /dev/null +++ b/examples/rp23/src/bin/i2c_slave.rs @@ -0,0 +1,132 @@ +//! This example shows how to use the 2040 as an i2c slave. +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::{I2C0, I2C1}; +use embassy_rp::{bind_interrupts, i2c, i2c_slave}; +use embassy_time::Timer; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + I2C0_IRQ => i2c::InterruptHandler; + I2C1_IRQ => i2c::InterruptHandler; +}); + +const DEV_ADDR: u8 = 0x42; + +#[embassy_executor::task] +async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { + info!("Device start"); + + let mut state = 0; + + loop { + let mut buf = [0u8; 128]; + match dev.listen(&mut buf).await { + Ok(i2c_slave::Command::GeneralCall(len)) => info!("Device received general call write: {}", buf[..len]), + Ok(i2c_slave::Command::Read) => loop { + match dev.respond_to_read(&[state]).await { + Ok(x) => match x { + i2c_slave::ReadStatus::Done => break, + i2c_slave::ReadStatus::NeedMoreBytes => (), + i2c_slave::ReadStatus::LeftoverBytes(x) => { + info!("tried to write {} extra bytes", x); + break; + } + }, + Err(e) => error!("error while responding {}", e), + } + }, + Ok(i2c_slave::Command::Write(len)) => info!("Device received write: {}", buf[..len]), + Ok(i2c_slave::Command::WriteRead(len)) => { + info!("device received write read: {:x}", buf[..len]); + match buf[0] { + // Set the state + 0xC2 => { + state = buf[1]; + match dev.respond_and_fill(&[state], 0x00).await { + Ok(read_status) => info!("response read status {}", read_status), + Err(e) => error!("error while responding {}", e), + } + } + // Reset State + 0xC8 => { + state = 0; + match dev.respond_and_fill(&[state], 0x00).await { + Ok(read_status) => info!("response read status {}", read_status), + Err(e) => error!("error while responding {}", e), + } + } + x => error!("Invalid Write Read {:x}", x), + } + } + Err(e) => error!("{}", e), + } + } +} + +#[embassy_executor::task] +async fn controller_task(mut con: i2c::I2c<'static, I2C0, i2c::Async>) { + info!("Controller start"); + + loop { + let mut resp_buff = [0u8; 2]; + for i in 0..10 { + match con.write_read(DEV_ADDR, &[0xC2, i], &mut resp_buff).await { + Ok(_) => info!("write_read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + + Timer::after_millis(100).await; + } + match con.read(DEV_ADDR, &mut resp_buff).await { + Ok(_) => info!("read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + match con.write_read(DEV_ADDR, &[0xC8], &mut resp_buff).await { + Ok(_) => info!("write_read response: {}", resp_buff), + Err(e) => error!("Error writing {}", e), + } + Timer::after_millis(100).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let d_sda = p.PIN_3; + let d_scl = p.PIN_2; + let mut config = i2c_slave::Config::default(); + config.addr = DEV_ADDR as u16; + let device = i2c_slave::I2cSlave::new(p.I2C1, d_sda, d_scl, Irqs, config); + + unwrap!(spawner.spawn(device_task(device))); + + let c_sda = p.PIN_1; + let c_scl = p.PIN_0; + let mut config = i2c::Config::default(); + config.frequency = 1_000_000; + let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); + + unwrap!(spawner.spawn(controller_task(controller))); +} diff --git a/examples/rp23/src/bin/interrupt.rs b/examples/rp23/src/bin/interrupt.rs new file mode 100644 index 000000000..ee3d9bfe7 --- /dev/null +++ b/examples/rp23/src/bin/interrupt.rs @@ -0,0 +1,109 @@ +//! This example shows how you can use raw interrupt handlers alongside embassy. +//! The example also showcases some of the options available for sharing resources/data. +//! +//! In the example, an ADC reading is triggered every time the PWM wraps around. +//! The sample data is sent down a channel, to be processed inside a low priority task. +//! The processed data is then used to adjust the PWM duty cycle, once every second. + +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{self, Adc, Blocking}; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_rp::interrupt; +use embassy_rp::pwm::{Config, Pwm}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::channel::Channel; +use embassy_time::{Duration, Ticker}; +use portable_atomic::{AtomicU32, Ordering}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +static COUNTER: AtomicU32 = AtomicU32::new(0); +static PWM: Mutex>> = Mutex::new(RefCell::new(None)); +static ADC: Mutex, adc::Channel)>>> = + Mutex::new(RefCell::new(None)); +static ADC_VALUES: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + embassy_rp::pac::SIO.spinlock(31).write_value(1); + let p = embassy_rp::init(Default::default()); + + let adc = Adc::new_blocking(p.ADC, Default::default()); + let p26 = adc::Channel::new_pin(p.PIN_26, Pull::None); + ADC.lock(|a| a.borrow_mut().replace((adc, p26))); + + let pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, Default::default()); + PWM.lock(|p| p.borrow_mut().replace(pwm)); + + // Enable the interrupt for pwm slice 4 + embassy_rp::pac::PWM.irq0_inte().modify(|w| w.set_ch4(true)); + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::PWM_IRQ_WRAP_0); + } + + // Tasks require their resources to have 'static lifetime + // No Mutex needed when sharing within the same executor/prio level + static AVG: StaticCell> = StaticCell::new(); + let avg = AVG.init(Default::default()); + spawner.must_spawn(processing(avg)); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + let freq = COUNTER.swap(0, Ordering::Relaxed); + info!("pwm freq: {:?} Hz", freq); + info!("adc average: {:?}", avg.get()); + + // Update the pwm duty cycle, based on the averaged adc reading + let mut config = Config::default(); + config.compare_b = ((avg.get() as f32 / 4095.0) * config.top as f32) as _; + PWM.lock(|p| p.borrow_mut().as_mut().unwrap().set_config(&config)); + } +} + +#[embassy_executor::task] +async fn processing(avg: &'static Cell) { + let mut buffer: heapless::HistoryBuffer = Default::default(); + loop { + let val = ADC_VALUES.receive().await; + buffer.write(val); + let sum: u32 = buffer.iter().map(|x| *x as u32).sum(); + avg.set(sum / buffer.len() as u32); + } +} + +#[interrupt] +fn PWM_IRQ_WRAP_0() { + critical_section::with(|cs| { + let mut adc = ADC.borrow(cs).borrow_mut(); + let (adc, p26) = adc.as_mut().unwrap(); + let val = adc.blocking_read(p26).unwrap(); + ADC_VALUES.try_send(val).ok(); + + // Clear the interrupt, so we don't immediately re-enter this irq handler + PWM.borrow(cs).borrow_mut().as_mut().unwrap().clear_wrapped(); + }); + COUNTER.fetch_add(1, Ordering::Relaxed); +} diff --git a/examples/rp23/src/bin/multicore.rs b/examples/rp23/src/bin/multicore.rs new file mode 100644 index 000000000..9ab43d7a5 --- /dev/null +++ b/examples/rp23/src/bin/multicore.rs @@ -0,0 +1,81 @@ +//! This example shows how to send messages between the two cores in the RP2040 chip. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Executor; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL: Channel = Channel::new(); + +enum LedState { + On, + Off, +} + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + let led = Output::new(p.PIN_25, Level::Low); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); + }, + ); + + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("Hello from core 0"); + loop { + CHANNEL.send(LedState::On).await; + Timer::after_millis(100).await; + CHANNEL.send(LedState::Off).await; + Timer::after_millis(400).await; + } +} + +#[embassy_executor::task] +async fn core1_task(mut led: Output<'static>) { + info!("Hello from core 1"); + loop { + match CHANNEL.receive().await { + LedState::On => led.set_high(), + LedState::Off => led.set_low(), + } + } +} diff --git a/examples/rp23/src/bin/multiprio.rs b/examples/rp23/src/bin/multiprio.rs new file mode 100644 index 000000000..27cd3656e --- /dev/null +++ b/examples/rp23/src/bin/multiprio.rs @@ -0,0 +1,160 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::block::ImageDef; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_time::{Instant, Timer, TICK_HZ}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::task] +async fn run_high() { + loop { + info!(" [high] tick!"); + Timer::after_ticks(673740).await; + } +} + +#[embassy_executor::task] +async fn run_med() { + loop { + let start = Instant::now(); + info!(" [med] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + embassy_time::block_for(embassy_time::Duration::from_secs(1)); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!(" [med] done in {} ms", ms); + + Timer::after_ticks(53421).await; + } +} + +#[embassy_executor::task] +async fn run_low() { + loop { + let start = Instant::now(); + info!("[low] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + embassy_time::block_for(embassy_time::Duration::from_secs(2)); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!("[low] done in {} ms", ms); + + Timer::after_ticks(82983).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_rp::init(Default::default()); + + // High-priority executor: SWI_IRQ_1, priority level 2 + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + let spawner = EXECUTOR_HIGH.start(interrupt::SWI_IRQ_1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: SWI_IRQ_0, priority level 3 + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_MED.start(interrupt::SWI_IRQ_0); + unwrap!(spawner.spawn(run_med())); + + // Low priority executor: runs in thread mode, using WFE/SEV + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + unwrap!(spawner.spawn(run_low())); + }); +} diff --git a/examples/rp23/src/bin/otp.rs b/examples/rp23/src/bin/otp.rs new file mode 100644 index 000000000..106e514ca --- /dev/null +++ b/examples/rp23/src/bin/otp.rs @@ -0,0 +1,46 @@ +//! This example shows reading the OTP constants on the RP235x. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::otp; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"OTP Read Example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"OTP Read Example"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _ = embassy_rp::init(Default::default()); + // + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after_millis(10).await; + + let chip_id = unwrap!(otp::get_chipid()); + info!("Unique id:{:X}", chip_id); + + let private_rand = unwrap!(otp::get_private_random_number()); + info!("Private Rand:{:X}", private_rand); + + loop { + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/pio_async.rs b/examples/rp23/src/bin/pio_async.rs new file mode 100644 index 000000000..231afc80e --- /dev/null +++ b/examples/rp23/src/bin/pio_async.rs @@ -0,0 +1,145 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) { + // Setup sm0 + + // Send data serially to pin + let prg = pio_proc::pio_asm!( + ".origin 16", + "set pindirs, 1", + ".wrap_target", + "out pins,1 [19]", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + cfg.clock_divider = (U56F8!(125_000_000) / 20 / 200).to_fixed(); + cfg.shift_out.auto_fill = true; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm0(mut sm: StateMachine<'static, PIO0, 0>) { + sm.set_enable(true); + + let mut v = 0x0f0caffa; + loop { + sm.tx().wait_push(v).await; + v ^= 0xffff; + info!("Pushed {:032b} to FIFO", v); + } +} + +fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 1>) { + // Setupm sm1 + + // Read 0b10101 repeatedly until ISR is full + let prg = pio_proc::pio_asm!( + // + ".origin 8", + "set x, 0x15", + ".wrap_target", + "in x, 5 [31]", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Right; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm1(mut sm: StateMachine<'static, PIO0, 1>) { + sm.set_enable(true); + loop { + let rx = sm.rx().wait_pull().await; + info!("Pulled {:032b} from FIFO", rx); + } +} + +fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 2>) { + // Setup sm2 + + // Repeatedly trigger IRQ 3 + let prg = pio_proc::pio_asm!( + ".origin 0", + ".wrap_target", + "set x,10", + "delay:", + "jmp x-- delay [15]", + "irq 3 [15]", + ".wrap", + ); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm2(mut irq: Irq<'static, PIO0, 3>, mut sm: StateMachine<'static, PIO0, 2>) { + sm.set_enable(true); + loop { + irq.wait().await; + info!("IRQ trigged"); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + + let Pio { + mut common, + irq3, + mut sm0, + mut sm1, + mut sm2, + .. + } = Pio::new(pio, Irqs); + + setup_pio_task_sm0(&mut common, &mut sm0, p.PIN_0); + setup_pio_task_sm1(&mut common, &mut sm1); + setup_pio_task_sm2(&mut common, &mut sm2); + spawner.spawn(pio_task_sm0(sm0)).unwrap(); + spawner.spawn(pio_task_sm1(sm1)).unwrap(); + spawner.spawn(pio_task_sm2(irq3, sm2)).unwrap(); +} diff --git a/examples/rp23/src/bin/pio_dma.rs b/examples/rp23/src/bin/pio_dma.rs new file mode 100644 index 000000000..60fbcb83a --- /dev/null +++ b/examples/rp23/src/bin/pio_dma.rs @@ -0,0 +1,98 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; +use embassy_rp::{bind_interrupts, Peripheral}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn swap_nibbles(v: u32) -> u32 { + let v = (v & 0x0f0f_0f0f) << 4 | (v & 0xf0f0_f0f0) >> 4; + let v = (v & 0x00ff_00ff) << 8 | (v & 0xff00_ff00) >> 8; + (v & 0x0000_ffff) << 16 | (v & 0xffff_0000) >> 16 +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + sm0: mut sm, + .. + } = Pio::new(pio, Irqs); + + let prg = pio_proc::pio_asm!( + ".origin 0", + "set pindirs,1", + ".wrap_target", + "set y,7", + "loop:", + "out x,4", + "in x,4", + "jmp y--, loop", + ".wrap", + ); + + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(10_000)).to_fixed(); + cfg.shift_in = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Right, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + let mut dma_out_ref = p.DMA_CH0.into_ref(); + let mut dma_in_ref = p.DMA_CH1.into_ref(); + let mut dout = [0x12345678u32; 29]; + for i in 1..dout.len() { + dout[i] = (dout[i - 1] & 0x0fff_ffff) * 13 + 7; + } + let mut din = [0u32; 29]; + loop { + let (rx, tx) = sm.rx_tx(); + join( + tx.dma_push(dma_out_ref.reborrow(), &dout), + rx.dma_pull(dma_in_ref.reborrow(), &mut din), + ) + .await; + for i in 0..din.len() { + assert_eq!(din[i], swap_nibbles(dout[i])); + } + info!("Swapped {} words", dout.len()); + } +} diff --git a/examples/rp23/src/bin/pio_hd44780.rs b/examples/rp23/src/bin/pio_hd44780.rs new file mode 100644 index 000000000..92aa858f9 --- /dev/null +++ b/examples/rp23/src/bin/pio_hd44780.rs @@ -0,0 +1,255 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with a HD44780 display. +//! See (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{ + Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use embassy_rp::pwm::{self, Pwm}; +use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(pub struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // this test assumes a 2x16 HD44780 display attached as follow: + // rs = PIN0 + // rw = PIN1 + // e = PIN2 + // db4 = PIN3 + // db5 = PIN4 + // db6 = PIN5 + // db7 = PIN6 + // additionally a pwm signal for a bias voltage charge pump is provided on pin 15, + // allowing direct connection of the display to the RP2040 without level shifters. + let p = embassy_rp::init(Default::default()); + + let _pwm = Pwm::new_output_b(p.PWM_SLICE7, p.PIN_15, { + let mut c = pwm::Config::default(); + c.divider = 125.into(); + c.top = 100; + c.compare_b = 50; + c + }); + + let mut hd = HD44780::new( + p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + ) + .await; + + loop { + struct Buf([u8; N], usize); + impl Write for Buf { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + for b in s.as_bytes() { + if self.1 >= N { + return Err(core::fmt::Error); + } + self.0[self.1] = *b; + self.1 += 1; + } + Ok(()) + } + } + let mut buf = Buf([0; 16], 0); + write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap(); + hd.add_line(&buf.0[0..buf.1]).await; + Timer::after_secs(1).await; + } +} + +pub struct HD44780<'l> { + dma: PeripheralRef<'l, AnyChannel>, + sm: StateMachine<'l, PIO0, 0>, + + buf: [u8; 40], +} + +impl<'l> HD44780<'l> { + pub async fn new( + pio: impl Peripheral

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

+ 'l, + rs: impl PioPin, + rw: impl PioPin, + e: impl PioPin, + db4: impl PioPin, + db5: impl PioPin, + db6: impl PioPin, + db7: impl PioPin, + ) -> HD44780<'l> { + into_ref!(dma); + + let Pio { + mut common, + mut irq0, + mut sm0, + .. + } = Pio::new(pio, irq); + + // takes command words ( <0:4>) + let prg = pio_proc::pio_asm!( + r#" + .side_set 1 opt + .origin 20 + + loop: + out x, 24 + delay: + jmp x--, delay + out pins, 4 side 1 + out null, 4 side 0 + jmp !osre, loop + irq 0 + "#, + ); + + let rs = common.make_pio_pin(rs); + let rw = common.make_pio_pin(rw); + let e = common.make_pio_pin(e); + let db4 = common.make_pio_pin(db4); + let db5 = common.make_pio_pin(db5); + let db6 = common.make_pio_pin(db6); + let db7 = common.make_pio_pin(db7); + + sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); + + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[&e]); + cfg.clock_divider = 125u8.into(); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Left, + threshold: 32, + }; + cfg.fifo_join = FifoJoin::TxOnly; + sm0.set_config(&cfg); + + sm0.set_enable(true); + // init to 8 bit thrice + sm0.tx().push((50000 << 8) | 0x30); + sm0.tx().push((5000 << 8) | 0x30); + sm0.tx().push((200 << 8) | 0x30); + // init 4 bit + sm0.tx().push((200 << 8) | 0x20); + // set font and lines + sm0.tx().push((50 << 8) | 0x20); + sm0.tx().push(0b1100_0000); + + irq0.wait().await; + sm0.set_enable(false); + + // takes command sequences ( , data...) + // many side sets are only there to free up a delay bit! + let prg = pio_proc::pio_asm!( + r#" + .origin 27 + .side_set 1 + + .wrap_target + pull side 0 + out x 1 side 0 ; !rs + out y 7 side 0 ; #data - 1 + + ; rs/rw to e: >= 60ns + ; e high time: >= 500ns + ; e low time: >= 500ns + ; read data valid after e falling: ~5ns + ; write data hold after e falling: ~10ns + + loop: + pull side 0 + jmp !x data side 0 + command: + set pins 0b00 side 0 + jmp shift side 0 + data: + set pins 0b01 side 0 + shift: + out pins 4 side 1 [9] + nop side 0 [9] + out pins 4 side 1 [9] + mov osr null side 0 [7] + out pindirs 4 side 0 + set pins 0b10 side 0 + busy: + nop side 1 [9] + jmp pin more side 0 [9] + mov osr ~osr side 1 [9] + nop side 0 [4] + out pindirs 4 side 0 + jmp y-- loop side 0 + .wrap + more: + nop side 1 [9] + jmp busy side 0 [9] + "# + ); + + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&prg.program), &[&e]); + cfg.clock_divider = 8u8.into(); // ~64ns/insn + cfg.set_jmp_pin(&db7); + cfg.set_set_pins(&[&rs, &rw]); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.fifo_join = FifoJoin::TxOnly; + sm0.set_config(&cfg); + + sm0.set_enable(true); + + // display on and cursor on and blinking, reset display + sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; + + Self { + dma: dma.map_into(), + sm: sm0, + buf: [0x20; 40], + } + } + + pub async fn add_line(&mut self, s: &[u8]) { + // move cursor to 0:0, prepare 16 characters + self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); + // move line 2 up + self.buf.copy_within(22..38, 3); + // move cursor to 1:0, prepare 16 characters + self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); + // file line 2 with spaces + self.buf[22..38].fill(0x20); + // copy input line + let len = s.len().min(16); + self.buf[22..22 + len].copy_from_slice(&s[0..len]); + // set cursor to 1:15 + self.buf[38..].copy_from_slice(&[0x80, 0xcf]); + + self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; + } +} diff --git a/examples/rp23/src/bin/pio_i2s.rs b/examples/rp23/src/bin/pio_i2s.rs new file mode 100644 index 000000000..d6d2d0ade --- /dev/null +++ b/examples/rp23/src/bin/pio_i2s.rs @@ -0,0 +1,140 @@ +//! This example shows generating audio and sending it to a connected i2s DAC using the PIO +//! module of the RP2040. +//! +//! Connect the i2s DAC as follows: +//! bclk : GPIO 18 +//! lrc : GPIO 19 +//! din : GPIO 20 +//! Then hold down the boot select button to trigger a rising triangle waveform. + +#![no_std] +#![no_main] + +use core::mem; + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; +use embassy_rp::{bind_interrupts, Peripheral}; +use fixed::traits::ToFixed; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const SAMPLE_RATE: u32 = 48_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Setup pio state machine for i2s output + let mut pio = Pio::new(p.PIO0, Irqs); + + #[rustfmt::skip] + let pio_program = pio_proc::pio_asm!( + ".side_set 2", + " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock + "left_data:", + " out pins, 1 side 0b00", + " jmp x-- left_data side 0b01", + " out pins 1 side 0b10", + " set x, 14 side 0b11", + "right_data:", + " out pins 1 side 0b10", + " jmp x-- right_data side 0b11", + " out pins 1 side 0b00", + ); + + let bit_clock_pin = p.PIN_18; + let left_right_clock_pin = p.PIN_19; + let data_pin = p.PIN_20; + + let data_pin = pio.common.make_pio_pin(data_pin); + let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program( + &pio.common.load_program(&pio_program.program), + &[&bit_clock_pin, &left_right_clock_pin], + ); + cfg.set_out_pins(&[&data_pin]); + const BIT_DEPTH: u32 = 16; + const CHANNELS: u32 = 2; + let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; + cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); + cfg.shift_out = ShiftConfig { + threshold: 32, + direction: ShiftDirection::Left, + auto_fill: true, + }; + // join fifos to have twice the time to start the next dma transfer + cfg.fifo_join = FifoJoin::TxOnly; + cfg + }; + pio.sm0.set_config(&cfg); + pio.sm0.set_pin_dirs( + embassy_rp::pio::Direction::Out, + &[&data_pin, &left_right_clock_pin, &bit_clock_pin], + ); + + // create two audio buffers (back and front) which will take turns being + // filled with new audio data and being sent to the pio fifo using dma + const BUFFER_SIZE: usize = 960; + static DMA_BUFFER: StaticCell<[u32; BUFFER_SIZE * 2]> = StaticCell::new(); + let dma_buffer = DMA_BUFFER.init_with(|| [0u32; BUFFER_SIZE * 2]); + let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); + + // start pio state machine + pio.sm0.set_enable(true); + let tx = pio.sm0.tx(); + let mut dma_ref = p.DMA_CH0.into_ref(); + + let mut fade_value: i32 = 0; + let mut phase: i32 = 0; + + loop { + // trigger transfer of front buffer data to the pio fifo + // but don't await the returned future, yet + let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); + + // fade in audio + let fade_target = i32::MAX; + + // fill back buffer with fresh audio samples before awaiting the dma future + for s in back_buffer.iter_mut() { + // exponential approach of fade_value => fade_target + fade_value += (fade_target - fade_value) >> 14; + // generate triangle wave with amplitude and frequency based on fade value + phase = (phase + (fade_value >> 22)) & 0xffff; + let triangle_sample = (phase as i16 as i32).abs() - 16384; + let sample = (triangle_sample * (fade_value >> 15)) >> 16; + // duplicate mono sample into lower and upper half of dma word + *s = (sample as u16 as u32) * 0x10001; + } + + // now await the dma future. once the dma finishes, the next buffer needs to be queued + // within DMA_DEPTH / SAMPLE_RATE = 8 / 48000 seconds = 166us + dma_future.await; + mem::swap(&mut back_buffer, &mut front_buffer); + } +} diff --git a/examples/rp23/src/bin/pio_pwm.rs b/examples/rp23/src/bin/pio_pwm.rs new file mode 100644 index 000000000..587f91ac3 --- /dev/null +++ b/examples/rp23/src/bin/pio_pwm.rs @@ -0,0 +1,133 @@ +//! This example shows how to create a pwm using the PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use core::time::Duration; + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Level; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; +use embassy_rp::{bind_interrupts, clocks}; +use embassy_time::Timer; +use pio::InstructionOperands; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +const REFRESH_INTERVAL: u64 = 20000; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub fn to_pio_cycles(duration: Duration) -> u32 { + (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow +} + +pub struct PwmPio<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { + pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { + let prg = pio_proc::pio_asm!( + ".side_set 1 opt" + "pull noblock side 0" + "mov x, osr" + "mov y, isr" + "countloop:" + "jmp x!=y noset" + "jmp skip side 1" + "noset:" + "nop" + "skip:" + "jmp y-- countloop" + ); + + pio.load_program(&prg.program); + let pin = pio.make_pio_pin(pin); + sm.set_pins(Level::High, &[&pin]); + sm.set_pin_dirs(Direction::Out, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[&pin]); + + sm.set_config(&cfg); + + Self { sm } + } + + pub fn start(&mut self) { + self.sm.set_enable(true); + } + + pub fn stop(&mut self) { + self.sm.set_enable(false); + } + + pub fn set_period(&mut self, duration: Duration) { + let is_enabled = self.sm.is_enabled(); + while !self.sm.tx().empty() {} // Make sure that the queue is empty + self.sm.set_enable(false); + self.sm.tx().push(to_pio_cycles(duration)); + unsafe { + self.sm.exec_instr( + InstructionOperands::PULL { + if_empty: false, + block: false, + } + .encode(), + ); + self.sm.exec_instr( + InstructionOperands::OUT { + destination: ::pio::OutDestination::ISR, + bit_count: 32, + } + .encode(), + ); + }; + if is_enabled { + self.sm.set_enable(true) // Enable if previously enabled + } + } + + pub fn set_level(&mut self, level: u32) { + self.sm.tx().push(level); + } + + pub fn write(&mut self, duration: Duration) { + self.set_level(to_pio_cycles(duration)); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // Note that PIN_25 is the led pin on the Pico + let mut pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_25); + pwm_pio.set_period(Duration::from_micros(REFRESH_INTERVAL)); + pwm_pio.start(); + + let mut duration = 0; + loop { + duration = (duration + 1) % 1000; + pwm_pio.write(Duration::from_micros(duration)); + Timer::after_millis(1).await; + } +} diff --git a/examples/rp23/src/bin/pio_rotary_encoder.rs b/examples/rp23/src/bin/pio_rotary_encoder.rs new file mode 100644 index 000000000..c147351e8 --- /dev/null +++ b/examples/rp23/src/bin/pio_rotary_encoder.rs @@ -0,0 +1,95 @@ +//! This example shows how to use the PIO module in the RP2040 to read a quadrature rotary encoder. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_rp::peripherals::PIO0; +use embassy_rp::{bind_interrupts, pio}; +use fixed::traits::ToFixed; +use pio::{Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftDirection, StateMachine}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub struct PioEncoder<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin_a: impl PioPin, + pin_b: impl PioPin, + ) -> Self { + let mut pin_a = pio.make_pio_pin(pin_a); + let mut pin_b = pio.make_pio_pin(pin_b); + pin_a.set_pull(Pull::Up); + pin_b.set_pull(Pull::Up); + sm.set_pin_dirs(pio::Direction::In, &[&pin_a, &pin_b]); + + let prg = pio_proc::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); + + let mut cfg = Config::default(); + cfg.set_in_pins(&[&pin_a, &pin_b]); + cfg.fifo_join = FifoJoin::RxOnly; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.clock_divider = 10_000.to_fixed(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + pub async fn read(&mut self) -> Direction { + loop { + match self.sm.rx().wait_pull().await { + 0 => return Direction::CounterClockwise, + 1 => return Direction::Clockwise, + _ => {} + } + } + } +} + +pub enum Direction { + Clockwise, + CounterClockwise, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + let mut encoder = PioEncoder::new(&mut common, sm0, p.PIN_4, p.PIN_5); + + let mut count = 0; + loop { + info!("Count: {}", count); + count += match encoder.read().await { + Direction::Clockwise => 1, + Direction::CounterClockwise => -1, + }; + } +} diff --git a/examples/rp23/src/bin/pio_servo.rs b/examples/rp23/src/bin/pio_servo.rs new file mode 100644 index 000000000..5e8714178 --- /dev/null +++ b/examples/rp23/src/bin/pio_servo.rs @@ -0,0 +1,223 @@ +//! This example shows how to create a pwm using the PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +use core::time::Duration; + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Level; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Pio, PioPin, StateMachine}; +use embassy_rp::{bind_interrupts, clocks}; +use embassy_time::Timer; +use pio::InstructionOperands; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +const DEFAULT_MIN_PULSE_WIDTH: u64 = 1000; // uncalibrated default, the shortest duty cycle sent to a servo +const DEFAULT_MAX_PULSE_WIDTH: u64 = 2000; // uncalibrated default, the longest duty cycle sent to a servo +const DEFAULT_MAX_DEGREE_ROTATION: u64 = 160; // 160 degrees is typical +const REFRESH_INTERVAL: u64 = 20000; // The period of each cycle + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub fn to_pio_cycles(duration: Duration) -> u32 { + (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow +} + +pub struct PwmPio<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PwmPio<'d, T, SM> { + pub fn new(pio: &mut Common<'d, T>, mut sm: StateMachine<'d, T, SM>, pin: impl PioPin) -> Self { + let prg = pio_proc::pio_asm!( + ".side_set 1 opt" + "pull noblock side 0" + "mov x, osr" + "mov y, isr" + "countloop:" + "jmp x!=y noset" + "jmp skip side 1" + "noset:" + "nop" + "skip:" + "jmp y-- countloop" + ); + + pio.load_program(&prg.program); + let pin = pio.make_pio_pin(pin); + sm.set_pins(Level::High, &[&pin]); + sm.set_pin_dirs(Direction::Out, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&prg.program), &[&pin]); + + sm.set_config(&cfg); + + Self { sm } + } + + pub fn start(&mut self) { + self.sm.set_enable(true); + } + + pub fn stop(&mut self) { + self.sm.set_enable(false); + } + + pub fn set_period(&mut self, duration: Duration) { + let is_enabled = self.sm.is_enabled(); + while !self.sm.tx().empty() {} // Make sure that the queue is empty + self.sm.set_enable(false); + self.sm.tx().push(to_pio_cycles(duration)); + unsafe { + self.sm.exec_instr( + InstructionOperands::PULL { + if_empty: false, + block: false, + } + .encode(), + ); + self.sm.exec_instr( + InstructionOperands::OUT { + destination: ::pio::OutDestination::ISR, + bit_count: 32, + } + .encode(), + ); + }; + if is_enabled { + self.sm.set_enable(true) // Enable if previously enabled + } + } + + pub fn set_level(&mut self, level: u32) { + self.sm.tx().push(level); + } + + pub fn write(&mut self, duration: Duration) { + self.set_level(to_pio_cycles(duration)); + } +} + +pub struct ServoBuilder<'d, T: Instance, const SM: usize> { + pwm: PwmPio<'d, T, SM>, + period: Duration, + min_pulse_width: Duration, + max_pulse_width: Duration, + max_degree_rotation: u64, +} + +impl<'d, T: Instance, const SM: usize> ServoBuilder<'d, T, SM> { + pub fn new(pwm: PwmPio<'d, T, SM>) -> Self { + Self { + pwm, + period: Duration::from_micros(REFRESH_INTERVAL), + min_pulse_width: Duration::from_micros(DEFAULT_MIN_PULSE_WIDTH), + max_pulse_width: Duration::from_micros(DEFAULT_MAX_PULSE_WIDTH), + max_degree_rotation: DEFAULT_MAX_DEGREE_ROTATION, + } + } + + pub fn set_period(mut self, duration: Duration) -> Self { + self.period = duration; + self + } + + pub fn set_min_pulse_width(mut self, duration: Duration) -> Self { + self.min_pulse_width = duration; + self + } + + pub fn set_max_pulse_width(mut self, duration: Duration) -> Self { + self.max_pulse_width = duration; + self + } + + pub fn set_max_degree_rotation(mut self, degree: u64) -> Self { + self.max_degree_rotation = degree; + self + } + + pub fn build(mut self) -> Servo<'d, T, SM> { + self.pwm.set_period(self.period); + Servo { + pwm: self.pwm, + min_pulse_width: self.min_pulse_width, + max_pulse_width: self.max_pulse_width, + max_degree_rotation: self.max_degree_rotation, + } + } +} + +pub struct Servo<'d, T: Instance, const SM: usize> { + pwm: PwmPio<'d, T, SM>, + min_pulse_width: Duration, + max_pulse_width: Duration, + max_degree_rotation: u64, +} + +impl<'d, T: Instance, const SM: usize> Servo<'d, T, SM> { + pub fn start(&mut self) { + self.pwm.start(); + } + + pub fn stop(&mut self) { + self.pwm.stop(); + } + + pub fn write_time(&mut self, duration: Duration) { + self.pwm.write(duration); + } + + pub fn rotate(&mut self, degree: u64) { + let degree_per_nano_second = (self.max_pulse_width.as_nanos() as u64 - self.min_pulse_width.as_nanos() as u64) + / self.max_degree_rotation; + let mut duration = + Duration::from_nanos(degree * degree_per_nano_second + self.min_pulse_width.as_nanos() as u64); + if self.max_pulse_width < duration { + duration = self.max_pulse_width; + } + + self.pwm.write(duration); + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + let pwm_pio = PwmPio::new(&mut common, sm0, p.PIN_1); + let mut servo = ServoBuilder::new(pwm_pio) + .set_max_degree_rotation(120) // Example of adjusting values for MG996R servo + .set_min_pulse_width(Duration::from_micros(350)) // This value was detemined by a rough experiment. + .set_max_pulse_width(Duration::from_micros(2600)) // Along with this value. + .build(); + + servo.start(); + + let mut degree = 0; + loop { + degree = (degree + 1) % 120; + servo.rotate(degree); + Timer::after_millis(50).await; + } +} diff --git a/examples/rp23/src/bin/pio_stepper.rs b/examples/rp23/src/bin/pio_stepper.rs new file mode 100644 index 000000000..24785443b --- /dev/null +++ b/examples/rp23/src/bin/pio_stepper.rs @@ -0,0 +1,183 @@ +//! This example shows how to use the PIO module in the RP2040 to implement a stepper motor driver +//! for a 5-wire stepper such as the 28BYJ-48. You can halt an ongoing rotation by dropping the future. + +#![no_std] +#![no_main] +use core::mem::{self, MaybeUninit}; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, Direction, Instance, InterruptHandler, Irq, Pio, PioPin, StateMachine}; +use embassy_time::{with_timeout, Duration, Timer}; +use fixed::traits::ToFixed; +use fixed::types::extra::U8; +use fixed::FixedU32; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub struct PioStepper<'d, T: Instance, const SM: usize> { + irq: Irq<'d, T, SM>, + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + irq: Irq<'d, T, SM>, + pin0: impl PioPin, + pin1: impl PioPin, + pin2: impl PioPin, + pin3: impl PioPin, + ) -> Self { + let prg = pio_proc::pio_asm!( + "pull block", + "mov x, osr", + "pull block", + "mov y, osr", + "jmp !x end", + "loop:", + "jmp !osre step", + "mov osr, y", + "step:", + "out pins, 4 [31]" + "jmp x-- loop", + "end:", + "irq 0 rel" + ); + let pin0 = pio.make_pio_pin(pin0); + let pin1 = pio.make_pio_pin(pin1); + let pin2 = pio.make_pio_pin(pin2); + let pin3 = pio.make_pio_pin(pin3); + sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); + let mut cfg = Config::default(); + cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); + cfg.clock_divider = (125_000_000 / (100 * 136)).to_fixed(); + cfg.use_program(&pio.load_program(&prg.program), &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { irq, sm } + } + + // Set pulse frequency + pub fn set_frequency(&mut self, freq: u32) { + let clock_divider: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); + assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(clock_divider >= 1, "clkdiv must be >= 1"); + self.sm.set_clock_divider(clock_divider); + self.sm.clkdiv_restart(); + } + + // Full step, one phase + pub async fn step(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await + } else { + self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await + } + } + + // Full step, two phase + pub async fn step2(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await + } else { + self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await + } + } + + // Half step + pub async fn step_half(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await + } else { + self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await + } + } + + async fn run(&mut self, steps: i32, pattern: u32) { + self.sm.tx().wait_push(steps as u32).await; + self.sm.tx().wait_push(pattern).await; + let drop = OnDrop::new(|| { + self.sm.clear_fifos(); + unsafe { + self.sm.exec_instr( + pio::InstructionOperands::JMP { + address: 0, + condition: pio::JmpCondition::Always, + } + .encode(), + ); + } + }); + self.irq.wait().await; + drop.defuse(); + } +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let Pio { + mut common, irq0, sm0, .. + } = Pio::new(p.PIO0, Irqs); + + let mut stepper = PioStepper::new(&mut common, sm0, irq0, p.PIN_4, p.PIN_5, p.PIN_6, p.PIN_7); + stepper.set_frequency(120); + loop { + info!("CW full steps"); + stepper.step(1000).await; + + info!("CCW full steps, drop after 1 sec"); + if let Err(_) = with_timeout(Duration::from_secs(1), stepper.step(i32::MIN)).await { + info!("Time's up!"); + Timer::after(Duration::from_secs(1)).await; + } + + info!("CW half steps"); + stepper.step_half(1000).await; + + info!("CCW half steps"); + stepper.step_half(-1000).await; + } +} diff --git a/examples/rp23/src/bin/pio_ws2812.rs b/examples/rp23/src/bin/pio_ws2812.rs new file mode 100644 index 000000000..00fe5e396 --- /dev/null +++ b/examples/rp23/src/bin/pio_ws2812.rs @@ -0,0 +1,176 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with WS2812 LED modules. +//! See (https://www.sparkfun.com/categories/tags/ws2812) + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{ + Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; +use embassy_time::{Duration, Ticker, Timer}; +use fixed::types::U24F8; +use fixed_macro::fixed; +use smart_leds::RGB8; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { + dma: PeripheralRef<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { + pub fn new( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: impl Peripheral

+ 'd, + pin: impl PioPin, + ) -> Self { + into_ref!(dma); + + // Setup sm0 + + // prepare the PIO program + let side_set = pio::SideSet::new(false, 1, false); + let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); + + const T1: u8 = 2; // start bit + const T2: u8 = 5; // data bit + const T3: u8 = 3; // stop bit + const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; + + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + let mut do_zero = a.label(); + a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); + a.bind(&mut wrap_target); + // Do stop bit + a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); + // Do start bit + a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); + // Do data bit = 1 + a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); + a.bind(&mut do_zero); + // Do data bit = 0 + a.nop_with_delay_and_side_set(T2 - 1, 0); + a.bind(&mut wrap_source); + + let prg = a.assemble_with_wrap(wrap_source, wrap_target); + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + cfg.use_program(&pio.load_program(&prg), &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + // TODO CLOCK_FREQ should come from embassy_rp + let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); + let ws2812_freq = fixed!(800: U24F8); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 24, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { + dma: dma.map_into(), + sm, + } + } + + pub async fn write(&mut self, colors: &[RGB8; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); + words[i] = word; + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words).await; + + Timer::after_micros(55).await; + } +} + +/// Input a value 0 to 255 to get a color value +/// The colours are a transition r - g - b - back to r. +fn wheel(mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + let p = embassy_rp::init(Default::default()); + + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit + // feather boards for the 2040 both have one built in. + const NUM_LEDS: usize = 1; + let mut data = [RGB8::default(); NUM_LEDS]; + + // Common neopixel pins: + // Thing plus: 8 + // Adafruit Feather: 16; Adafruit Feather+RFM95: 4 + let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); + + // Loop forever making RGB values and pushing them out to the WS2812. + let mut ticker = Ticker::every(Duration::from_millis(10)); + loop { + for j in 0..(256 * 5) { + debug!("New Colors:"); + for i in 0..NUM_LEDS { + data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); + debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); + } + ws2812.write(&data).await; + + ticker.next().await; + } + } +} diff --git a/examples/rp23/src/bin/pwm.rs b/examples/rp23/src/bin/pwm.rs new file mode 100644 index 000000000..bfc2c6f67 --- /dev/null +++ b/examples/rp23/src/bin/pwm.rs @@ -0,0 +1,44 @@ +//! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. +//! +//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::pwm::{Config, Pwm}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut c: Config = Default::default(); + c.top = 0x8000; + c.compare_b = 8; + let mut pwm = Pwm::new_output_b(p.PWM_SLICE4, p.PIN_25, c.clone()); + + loop { + info!("current LED duty cycle: {}/32768", c.compare_b); + Timer::after_secs(1).await; + c.compare_b = c.compare_b.rotate_left(4); + pwm.set_config(&c); + } +} diff --git a/examples/rp23/src/bin/pwm_input.rs b/examples/rp23/src/bin/pwm_input.rs new file mode 100644 index 000000000..b65f2778b --- /dev/null +++ b/examples/rp23/src/bin/pwm_input.rs @@ -0,0 +1,41 @@ +//! This example shows how to use the PWM module to measure the frequency of an input signal. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_rp::pwm::{Config, InputMode, Pwm}; +use embassy_time::{Duration, Ticker}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let cfg: Config = Default::default(); + let pwm = Pwm::new_input(p.PWM_SLICE2, p.PIN_5, Pull::None, InputMode::RisingEdge, cfg); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + info!("Input frequency: {} Hz", pwm.counter()); + pwm.set_counter(0); + ticker.next().await; + } +} diff --git a/examples/rp23/src/bin/rosc.rs b/examples/rp23/src/bin/rosc.rs new file mode 100644 index 000000000..f65b236b1 --- /dev/null +++ b/examples/rp23/src/bin/rosc.rs @@ -0,0 +1,46 @@ +//! This example test the RP Pico on board LED. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::{clocks, gpio}; +use embassy_time::Timer; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_rp::config::Config::default(); + config.clocks = clocks::ClockConfig::rosc(); + let p = embassy_rp::init(config); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + info!("led on!"); + led.set_high(); + Timer::after_secs(1).await; + + info!("led off!"); + led.set_low(); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/shared_bus.rs b/examples/rp23/src/bin/shared_bus.rs new file mode 100644 index 000000000..b3fde13e3 --- /dev/null +++ b/examples/rp23/src/bin/shared_bus.rs @@ -0,0 +1,130 @@ +//! This example shows how to share (async) I2C and SPI buses between multiple devices. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; +use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{AnyPin, Level, Output}; +use embassy_rp::i2c::{self, I2c, InterruptHandler}; +use embassy_rp::peripherals::{I2C1, SPI1}; +use embassy_rp::spi::{self, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +type Spi1Bus = Mutex>; +type I2c1Bus = Mutex>; + +bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + // Shared I2C bus + let i2c = I2c::new_async(p.I2C1, p.PIN_15, p.PIN_14, Irqs, i2c::Config::default()); + static I2C_BUS: StaticCell = StaticCell::new(); + let i2c_bus = I2C_BUS.init(Mutex::new(i2c)); + + spawner.must_spawn(i2c_task_a(i2c_bus)); + spawner.must_spawn(i2c_task_b(i2c_bus)); + + // Shared SPI bus + let spi_cfg = spi::Config::default(); + let spi = Spi::new(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, spi_cfg); + static SPI_BUS: StaticCell = StaticCell::new(); + let spi_bus = SPI_BUS.init(Mutex::new(spi)); + + // Chip select pins for the SPI devices + let cs_a = Output::new(AnyPin::from(p.PIN_0), Level::High); + let cs_b = Output::new(AnyPin::from(p.PIN_1), Level::High); + + spawner.must_spawn(spi_task_a(spi_bus, cs_a)); + spawner.must_spawn(spi_task_b(spi_bus, cs_b)); +} + +#[embassy_executor::task] +async fn i2c_task_a(i2c_bus: &'static I2c1Bus) { + let i2c_dev = I2cDevice::new(i2c_bus); + let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xC0); + loop { + info!("i2c task A"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn i2c_task_b(i2c_bus: &'static I2c1Bus) { + let i2c_dev = I2cDevice::new(i2c_bus); + let _sensor = DummyI2cDeviceDriver::new(i2c_dev, 0xDE); + loop { + info!("i2c task B"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn spi_task_a(spi_bus: &'static Spi1Bus, cs: Output<'static>) { + let spi_dev = SpiDevice::new(spi_bus, cs); + let _sensor = DummySpiDeviceDriver::new(spi_dev); + loop { + info!("spi task A"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn spi_task_b(spi_bus: &'static Spi1Bus, cs: Output<'static>) { + let spi_dev = SpiDevice::new(spi_bus, cs); + let _sensor = DummySpiDeviceDriver::new(spi_dev); + loop { + info!("spi task B"); + Timer::after_secs(1).await; + } +} + +// Dummy I2C device driver, using `embedded-hal-async` +struct DummyI2cDeviceDriver { + _i2c: I2C, +} + +impl DummyI2cDeviceDriver { + fn new(i2c_dev: I2C, _address: u8) -> Self { + Self { _i2c: i2c_dev } + } +} + +// Dummy SPI device driver, using `embedded-hal-async` +struct DummySpiDeviceDriver { + _spi: SPI, +} + +impl DummySpiDeviceDriver { + fn new(spi_dev: SPI) -> Self { + Self { _spi: spi_dev } + } +} diff --git a/examples/rp23/src/bin/sharing.rs b/examples/rp23/src/bin/sharing.rs new file mode 100644 index 000000000..4a3301cfd --- /dev/null +++ b/examples/rp23/src/bin/sharing.rs @@ -0,0 +1,165 @@ +//! This example shows some common strategies for sharing resources between tasks. +//! +//! We demonstrate five different ways of sharing, covering different use cases: +//! - Atomics: This method is used for simple values, such as bool and u8..u32 +//! - Blocking Mutex: This is used for sharing non-async things, using Cell/RefCell for interior mutability. +//! - Async Mutex: This is used for sharing async resources, where you need to hold the lock across await points. +//! The async Mutex has interior mutability built-in, so no RefCell is needed. +//! - Cell: For sharing Copy types between tasks running on the same executor. +//! - RefCell: When you want &mut access to a value shared between tasks running on the same executor. +//! +//! More information: https://embassy.dev/book/#_sharing_peripherals_between_tasks + +#![no_std] +#![no_main] + +use core::cell::{Cell, RefCell}; +use core::sync::atomic::{AtomicU32, Ordering}; + +use cortex_m_rt::entry; +use defmt::info; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::block::ImageDef; +use embassy_rp::clocks::RoscRng; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{self, InterruptHandler, UartTx}; +use embassy_rp::{bind_interrupts, interrupt}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::{blocking_mutex, mutex}; +use embassy_time::{Duration, Ticker}; +use rand::RngCore; +use static_cell::{ConstStaticCell, StaticCell}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +type UartAsyncMutex = mutex::Mutex>; + +struct MyType { + inner: u32, +} + +static EXECUTOR_HI: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +// Use Atomics for simple values +static ATOMIC: AtomicU32 = AtomicU32::new(0); + +// Use blocking Mutex with Cell/RefCell for sharing non-async things +static MUTEX_BLOCKING: blocking_mutex::Mutex> = + blocking_mutex::Mutex::new(RefCell::new(MyType { inner: 0 })); + +bind_interrupts!(struct Irqs { + UART0_IRQ => InterruptHandler; +}); + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_HI.on_interrupt() +} + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default()); + // Use the async Mutex for sharing async things (built-in interior mutability) + static UART: StaticCell = StaticCell::new(); + let uart = UART.init(mutex::Mutex::new(uart)); + + // High-priority executor: runs in interrupt mode + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_HI.start(interrupt::SWI_IRQ_0); + spawner.must_spawn(task_a(uart)); + + // Low priority executor: runs in thread mode + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + // No Mutex needed when sharing between tasks running on the same executor + + // Use Cell for Copy-types + static CELL: ConstStaticCell> = ConstStaticCell::new(Cell::new([0; 4])); + let cell = CELL.take(); + + // Use RefCell for &mut access + static REF_CELL: ConstStaticCell> = ConstStaticCell::new(RefCell::new(MyType { inner: 0 })); + let ref_cell = REF_CELL.take(); + + spawner.must_spawn(task_b(uart, cell, ref_cell)); + spawner.must_spawn(task_c(cell, ref_cell)); + }); +} + +#[embassy_executor::task] +async fn task_a(uart: &'static UartAsyncMutex) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + { + let mut uart = uart.lock().await; + uart.write(b"task a").await.unwrap(); + // The uart lock is released when it goes out of scope + } + + ATOMIC.store(random, Ordering::Relaxed); + + MUTEX_BLOCKING.lock(|x| x.borrow_mut().inner = random); + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_b(uart: &'static UartAsyncMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + let random = RoscRng.next_u32(); + + uart.lock().await.write(b"task b").await.unwrap(); + + cell.set(random.to_be_bytes()); + + ref_cell.borrow_mut().inner = random; + + ticker.next().await; + } +} + +#[embassy_executor::task] +async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell) { + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + info!("======================="); + + let atomic_val = ATOMIC.load(Ordering::Relaxed); + info!("atomic: {}", atomic_val); + + MUTEX_BLOCKING.lock(|x| { + let val = x.borrow().inner; + info!("blocking mutex: {}", val); + }); + + let cell_val = cell.get(); + info!("cell: {:?}", cell_val); + + let ref_cell_val = ref_cell.borrow().inner; + info!("ref_cell: {:?}", ref_cell_val); + + ticker.next().await; + } +} diff --git a/examples/rp23/src/bin/spi.rs b/examples/rp23/src/bin/spi.rs new file mode 100644 index 000000000..924873e60 --- /dev/null +++ b/examples/rp23/src/bin/spi.rs @@ -0,0 +1,61 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example for resistive touch sensor in Waveshare Pico-ResTouch + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::spi::Spi; +use embassy_rp::{gpio, spi}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // Example for resistive touch sensor in Waveshare Pico-ResTouch + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + + // create SPI + let mut config = spi::Config::default(); + config.frequency = 2_000_000; + let mut spi = Spi::new_blocking(p.SPI1, clk, mosi, miso, config); + + // Configure CS + let mut cs = Output::new(touch_cs, Level::Low); + + loop { + cs.set_low(); + let mut buf = [0x90, 0x00, 0x00, 0xd0, 0x00, 0x00]; + spi.blocking_transfer_in_place(&mut buf).unwrap(); + cs.set_high(); + + let x = (buf[1] as u32) << 5 | (buf[2] as u32) >> 3; + let y = (buf[4] as u32) << 5 | (buf[5] as u32) >> 3; + + info!("touch: {=u32} {=u32}", x, y); + } +} diff --git a/examples/rp23/src/bin/spi_async.rs b/examples/rp23/src/bin/spi_async.rs new file mode 100644 index 000000000..4a74f991c --- /dev/null +++ b/examples/rp23/src/bin/spi_async.rs @@ -0,0 +1,46 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! No specific hardware is specified in this example. If you connect pin 11 and 12 you should get the same data back. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + + let mut spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + info!("{:?}", rx_buf); + Timer::after_secs(1).await; + } +} diff --git a/examples/rp23/src/bin/spi_display.rs b/examples/rp23/src/bin/spi_display.rs new file mode 100644 index 000000000..71dd84658 --- /dev/null +++ b/examples/rp23/src/bin/spi_display.rs @@ -0,0 +1,327 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example written for a display using the ST7789 chip. Possibly the Waveshare Pico-ResTouch +//! (https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8) + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::*; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::spi; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Delay; +use embedded_graphics::image::{Image, ImageRawLE}; +use embedded_graphics::mono_font::ascii::FONT_10X20; +use embedded_graphics::mono_font::MonoTextStyle; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::*; +use embedded_graphics::primitives::{PrimitiveStyleBuilder, Rectangle}; +use embedded_graphics::text::Text; +use st7789::{Orientation, ST7789}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +use crate::my_display_interface::SPIDeviceInterface; +use crate::touch::Touch; + +const DISPLAY_FREQ: u32 = 64_000_000; +const TOUCH_FREQ: u32 = 200_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let bl = p.PIN_13; + let rst = p.PIN_15; + let display_cs = p.PIN_9; + let dcx = p.PIN_8; + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let touch_cs = p.PIN_16; + //let touch_irq = p.PIN_17; + + // create SPI + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + let mut touch_config = spi::Config::default(); + touch_config.frequency = TOUCH_FREQ; + touch_config.phase = spi::Phase::CaptureOnSecondTransition; + touch_config.polarity = spi::Polarity::IdleHigh; + + let spi: Spi<'_, _, Blocking> = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let touch_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(touch_cs, Level::High), touch_config); + + let mut touch = Touch::new(touch_spi); + + let dcx = Output::new(dcx, Level::Low); + let rst = Output::new(rst, Level::Low); + // dcx: 0 = command, 1 = data + + // Enable LCD backlight + let _bl = Output::new(bl, Level::High); + + // display interface abstraction from SPI and DC + let di = SPIDeviceInterface::new(display_spi, dcx); + + // create driver + let mut display = ST7789::new(di, rst, 240, 320); + + // initialize + display.init(&mut Delay).unwrap(); + + // set default orientation + display.set_orientation(Orientation::Landscape).unwrap(); + + display.clear(Rgb565::BLACK).unwrap(); + + let raw_image_data = ImageRawLE::new(include_bytes!("../../assets/ferris.raw"), 86); + let ferris = Image::new(&raw_image_data, Point::new(34, 68)); + + // Display the image + ferris.draw(&mut display).unwrap(); + + let style = MonoTextStyle::new(&FONT_10X20, Rgb565::GREEN); + Text::new( + "Hello embedded_graphics \n + embassy + RP2040!", + Point::new(20, 200), + style, + ) + .draw(&mut display) + .unwrap(); + + loop { + if let Some((x, y)) = touch.read() { + let style = PrimitiveStyleBuilder::new().fill_color(Rgb565::BLUE).build(); + + Rectangle::new(Point::new(x - 1, y - 1), Size::new(3, 3)) + .into_styled(style) + .draw(&mut display) + .unwrap(); + } + } +} + +/// Driver for the XPT2046 resistive touchscreen sensor +mod touch { + use embedded_hal_1::spi::{Operation, SpiDevice}; + + struct Calibration { + x1: i32, + x2: i32, + y1: i32, + y2: i32, + sx: i32, + sy: i32, + } + + const CALIBRATION: Calibration = Calibration { + x1: 3880, + x2: 340, + y1: 262, + y2: 3850, + sx: 320, + sy: 240, + }; + + pub struct Touch { + spi: SPI, + } + + impl Touch + where + SPI: SpiDevice, + { + pub fn new(spi: SPI) -> Self { + Self { spi } + } + + pub fn read(&mut self) -> Option<(i32, i32)> { + let mut x = [0; 2]; + let mut y = [0; 2]; + self.spi + .transaction(&mut [ + Operation::Write(&[0x90]), + Operation::Read(&mut x), + Operation::Write(&[0xd0]), + Operation::Read(&mut y), + ]) + .unwrap(); + + let x = (u16::from_be_bytes(x) >> 3) as i32; + let y = (u16::from_be_bytes(y) >> 3) as i32; + + let cal = &CALIBRATION; + + let x = ((x - cal.x1) * cal.sx / (cal.x2 - cal.x1)).clamp(0, cal.sx); + let y = ((y - cal.y1) * cal.sy / (cal.y2 - cal.y1)).clamp(0, cal.sy); + if x == 0 && y == 0 { + None + } else { + Some((x, y)) + } + } + } +} + +mod my_display_interface { + use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; + use embedded_hal_1::digital::OutputPin; + use embedded_hal_1::spi::SpiDevice; + + /// SPI display interface. + /// + /// This combines the SPI peripheral and a data/command pin + pub struct SPIDeviceInterface { + spi: SPI, + dc: DC, + } + + impl SPIDeviceInterface + where + SPI: SpiDevice, + DC: OutputPin, + { + /// Create new SPI interface for communciation with a display driver + pub fn new(spi: SPI, dc: DC) -> Self { + Self { spi, dc } + } + } + + impl WriteOnlyDataCommand for SPIDeviceInterface + where + SPI: SpiDevice, + DC: OutputPin, + { + fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { + // 1 = data, 0 = command + self.dc.set_low().map_err(|_| DisplayError::DCError)?; + + send_u8(&mut self.spi, cmds).map_err(|_| DisplayError::BusWriteError)?; + Ok(()) + } + + fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { + // 1 = data, 0 = command + self.dc.set_high().map_err(|_| DisplayError::DCError)?; + + send_u8(&mut self.spi, buf).map_err(|_| DisplayError::BusWriteError)?; + Ok(()) + } + } + + fn send_u8(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> { + match words { + DataFormat::U8(slice) => spi.write(slice), + DataFormat::U16(slice) => { + use byte_slice_cast::*; + spi.write(slice.as_byte_slice()) + } + DataFormat::U16LE(slice) => { + use byte_slice_cast::*; + for v in slice.as_mut() { + *v = v.to_le(); + } + spi.write(slice.as_byte_slice()) + } + DataFormat::U16BE(slice) => { + use byte_slice_cast::*; + for v in slice.as_mut() { + *v = v.to_be(); + } + spi.write(slice.as_byte_slice()) + } + DataFormat::U8Iter(iter) => { + let mut buf = [0; 32]; + let mut i = 0; + + for v in iter.into_iter() { + buf[i] = v; + i += 1; + + if i == buf.len() { + spi.write(&buf)?; + i = 0; + } + } + + if i > 0 { + spi.write(&buf[..i])?; + } + + Ok(()) + } + DataFormat::U16LEIter(iter) => { + use byte_slice_cast::*; + let mut buf = [0; 32]; + let mut i = 0; + + for v in iter.map(u16::to_le) { + buf[i] = v; + i += 1; + + if i == buf.len() { + spi.write(&buf.as_byte_slice())?; + i = 0; + } + } + + if i > 0 { + spi.write(&buf[..i].as_byte_slice())?; + } + + Ok(()) + } + DataFormat::U16BEIter(iter) => { + use byte_slice_cast::*; + let mut buf = [0; 64]; + let mut i = 0; + let len = buf.len(); + + for v in iter.map(u16::to_be) { + buf[i] = v; + i += 1; + + if i == len { + spi.write(&buf.as_byte_slice())?; + i = 0; + } + } + + if i > 0 { + spi.write(&buf[..i].as_byte_slice())?; + } + + Ok(()) + } + _ => unimplemented!(), + } + } +} diff --git a/examples/rp23/src/bin/spi_sdmmc.rs b/examples/rp23/src/bin/spi_sdmmc.rs new file mode 100644 index 000000000..dabf41ab8 --- /dev/null +++ b/examples/rp23/src/bin/spi_sdmmc.rs @@ -0,0 +1,98 @@ +//! This example shows how to use `embedded-sdmmc` with the RP2040 chip, over SPI. +//! +//! The example will attempt to read a file `MY_FILE.TXT` from the root directory +//! of the SD card and print its contents. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_embedded_hal::SetConfig; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::spi::Spi; +use embassy_rp::{gpio, spi}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::sdcard::{DummyCsPin, SdCard}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +struct DummyTimesource(); + +impl embedded_sdmmc::TimeSource for DummyTimesource { + fn get_timestamp(&self) -> embedded_sdmmc::Timestamp { + embedded_sdmmc::Timestamp { + year_since_1970: 0, + zero_indexed_month: 0, + zero_indexed_day: 0, + hours: 0, + minutes: 0, + seconds: 0, + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + embassy_rp::pac::SIO.spinlock(31).write_value(1); + let p = embassy_rp::init(Default::default()); + + // SPI clock needs to be running at <= 400kHz during initialization + let mut config = spi::Config::default(); + config.frequency = 400_000; + let spi = Spi::new_blocking(p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, config); + // Use a dummy cs pin here, for embedded-hal SpiDevice compatibility reasons + let spi_dev = ExclusiveDevice::new_no_delay(spi, DummyCsPin); + // Real cs pin + let cs = Output::new(p.PIN_16, Level::High); + + let sdcard = SdCard::new(spi_dev, cs, embassy_time::Delay); + info!("Card size is {} bytes", sdcard.num_bytes().unwrap()); + + // Now that the card is initialized, the SPI clock can go faster + let mut config = spi::Config::default(); + config.frequency = 16_000_000; + sdcard.spi(|dev| dev.bus_mut().set_config(&config)).ok(); + + // Now let's look for volumes (also known as partitions) on our block device. + // To do this we need a Volume Manager. It will take ownership of the block device. + let mut volume_mgr = embedded_sdmmc::VolumeManager::new(sdcard, DummyTimesource()); + + // Try and access Volume 0 (i.e. the first partition). + // The volume object holds information about the filesystem on that volume. + let mut volume0 = volume_mgr.open_volume(embedded_sdmmc::VolumeIdx(0)).unwrap(); + info!("Volume 0: {:?}", defmt::Debug2Format(&volume0)); + + // Open the root directory (mutably borrows from the volume). + let mut root_dir = volume0.open_root_dir().unwrap(); + + // Open a file called "MY_FILE.TXT" in the root directory + // This mutably borrows the directory. + let mut my_file = root_dir + .open_file_in_dir("MY_FILE.TXT", embedded_sdmmc::Mode::ReadOnly) + .unwrap(); + + // Print the contents of the file + while !my_file.is_eof() { + let mut buf = [0u8; 32]; + if let Ok(n) = my_file.read(&mut buf) { + info!("{:a}", buf[..n]); + } + } + + loop {} +} diff --git a/examples/rp23/src/bin/trng.rs b/examples/rp23/src/bin/trng.rs new file mode 100644 index 000000000..e146baa2e --- /dev/null +++ b/examples/rp23/src/bin/trng.rs @@ -0,0 +1,64 @@ +//! This example shows TRNG usage + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::TRNG; +use embassy_rp::trng::Trng; +use embassy_time::Timer; +use rand::RngCore; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + TRNG_IRQ => embassy_rp::trng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_rp::init(Default::default()); + + // Initialize the TRNG with default configuration + let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); + // A buffer to collect random bytes in. + let mut randomness = [0u8; 58]; + + let mut led = Output::new(peripherals.PIN_25, Level::Low); + + loop { + trng.fill_bytes(&mut randomness).await; + info!("Random bytes async {}", &randomness); + trng.blocking_fill_bytes(&mut randomness); + info!("Random bytes blocking {}", &randomness); + let random_u32 = trng.next_u32(); + let random_u64 = trng.next_u64(); + info!("Random u32 {} u64 {}", random_u32, random_u64); + // Random number of blinks between 0 and 31 + let blinks = random_u32 % 32; + for _ in 0..blinks { + led.set_high(); + Timer::after_millis(20).await; + led.set_low(); + Timer::after_millis(20).await; + } + Timer::after_millis(1000).await; + } +} diff --git a/examples/rp23/src/bin/uart.rs b/examples/rp23/src/bin/uart.rs new file mode 100644 index 000000000..0ffe0b293 --- /dev/null +++ b/examples/rp23/src/bin/uart.rs @@ -0,0 +1,40 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. Only output on pin 0 is tested. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::uart; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let config = uart::Config::default(); + let mut uart = uart::Uart::new_blocking(p.UART1, p.PIN_4, p.PIN_5, config); + uart.blocking_write("Hello World!\r\n".as_bytes()).unwrap(); + + loop { + uart.blocking_write("hello there!\r\n".as_bytes()).unwrap(); + cortex_m::asm::delay(1_000_000); + } +} diff --git a/examples/rp23/src/bin/uart_buffered_split.rs b/examples/rp23/src/bin/uart_buffered_split.rs new file mode 100644 index 000000000..4e69a20c4 --- /dev/null +++ b/examples/rp23/src/bin/uart_buffered_split.rs @@ -0,0 +1,73 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. If you connect pin 0 and 1 you should get the same data back. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config}; +use embassy_time::Timer; +use embedded_io_async::{Read, Write}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let (tx_pin, rx_pin, uart) = (p.PIN_0, p.PIN_1, p.UART0); + + static TX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + let tx_buf = &mut TX_BUF.init([0; 16])[..]; + static RX_BUF: StaticCell<[u8; 16]> = StaticCell::new(); + let rx_buf = &mut RX_BUF.init([0; 16])[..]; + let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let (mut tx, rx) = uart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + info!("Writing..."); + loop { + let data = [ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, + ]; + info!("TX {:?}", data); + tx.write_all(&data).await.unwrap(); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: BufferedUartRx<'static, UART0>) { + info!("Reading..."); + loop { + let mut buf = [0; 31]; + rx.read_exact(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/examples/rp23/src/bin/uart_r503.rs b/examples/rp23/src/bin/uart_r503.rs new file mode 100644 index 000000000..5ac8839e3 --- /dev/null +++ b/examples/rp23/src/bin/uart_r503.rs @@ -0,0 +1,173 @@ +#![no_std] +#![no_main] + +use defmt::{debug, error, info}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{Config, DataBits, InterruptHandler as UARTInterruptHandler, Parity, StopBits, Uart}; +use embassy_time::{with_timeout, Duration, Timer}; +use heapless::Vec; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(pub struct Irqs { + UART0_IRQ => UARTInterruptHandler; +}); + +const START: u16 = 0xEF01; +const ADDRESS: u32 = 0xFFFFFFFF; + +// ================================================================================ + +// Data package format +// Name Length Description +// ========================================================================================================== +// Start 2 bytes Fixed value of 0xEF01; High byte transferred first. +// Address 4 bytes Default value is 0xFFFFFFFF, which can be modified by command. +// High byte transferred first and at wrong adder value, module +// will reject to transfer. +// PID 1 byte 01H Command packet; +// 02H Data packet; Data packet shall not appear alone in executing +// processs, must follow command packet or acknowledge packet. +// 07H Acknowledge packet; +// 08H End of Data packet. +// LENGTH 2 bytes Refers to the length of package content (command packets and data packets) +// plus the length of Checksum (2 bytes). Unit is byte. Max length is 256 bytes. +// And high byte is transferred first. +// DATA - It can be commands, data, command’s parameters, acknowledge result, etc. +// (fingerprint character value, template are all deemed as data); +// SUM 2 bytes The arithmetic sum of package identifier, package length and all package +// contens. Overflowing bits are omitted. high byte is transferred first. + +// ================================================================================ + +// Checksum is calculated on 'length (2 bytes) + data (??)'. +fn compute_checksum(buf: Vec) -> u16 { + let mut checksum = 0u16; + + let check_end = buf.len(); + let checked_bytes = &buf[6..check_end]; + for byte in checked_bytes { + checksum += (*byte) as u16; + } + return checksum; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + + let p = embassy_rp::init(Default::default()); + + // Initialize the fingerprint scanner. + let mut config = Config::default(); + config.baudrate = 57600; + config.stop_bits = StopBits::STOP1; + config.data_bits = DataBits::DataBits8; + config.parity = Parity::ParityNone; + + let (uart, tx_pin, tx_dma, rx_pin, rx_dma) = (p.UART0, p.PIN_16, p.DMA_CH0, p.PIN_17, p.DMA_CH1); + let uart = Uart::new(uart, tx_pin, rx_pin, Irqs, tx_dma, rx_dma, config); + let (mut tx, mut rx) = uart.split(); + + let mut vec_buf: Vec = heapless::Vec::new(); + let mut data: Vec = heapless::Vec::new(); + + let mut speeds: Vec = heapless::Vec::new(); + let _ = speeds.push(0xC8); // Slow + let _ = speeds.push(0x20); // Medium + let _ = speeds.push(0x02); // Fast + + // Cycle through the three colours Red, Blue and Purple forever. + loop { + for colour in 1..=3 { + for speed in &speeds { + // Set the data first, because the length is dependent on that. + // However, we write the length bits before we do the data. + data.clear(); + let _ = data.push(0x01); // ctrl=Breathing light + let _ = data.push(*speed); + let _ = data.push(colour as u8); // colour=Red, Blue, Purple + let _ = data.push(0x00); // times=Infinite + + // Clear buffers + vec_buf.clear(); + + // START + let _ = vec_buf.extend_from_slice(&START.to_be_bytes()[..]); + + // ADDRESS + let _ = vec_buf.extend_from_slice(&ADDRESS.to_be_bytes()[..]); + + // PID + let _ = vec_buf.extend_from_slice(&[0x01]); + + // LENGTH + let len: u16 = (1 + data.len() + 2).try_into().unwrap(); + let _ = vec_buf.extend_from_slice(&len.to_be_bytes()[..]); + + // COMMAND + let _ = vec_buf.push(0x35); // Command: AuraLedConfig + + // DATA + let _ = vec_buf.extend_from_slice(&data); + + // SUM + let chk = compute_checksum(vec_buf.clone()); + let _ = vec_buf.extend_from_slice(&chk.to_be_bytes()[..]); + + // ===== + + // Send command buffer. + let data_write: [u8; 16] = vec_buf.clone().into_array().unwrap(); + debug!(" write='{:?}'", data_write[..]); + match tx.write(&data_write).await { + Ok(..) => info!("Write successful."), + Err(e) => error!("Write error: {:?}", e), + } + + // ===== + + // Read command buffer. + let mut read_buf: [u8; 1] = [0; 1]; // Can only read one byte at a time! + let mut data_read: Vec = heapless::Vec::new(); // Save buffer. + + info!("Attempting read."); + loop { + // Some commands, like `Img2Tz()` needs longer, but we hard-code this to 200ms + // for this command. + match with_timeout(Duration::from_millis(200), rx.read(&mut read_buf)).await { + Ok(..) => { + // Extract and save read byte. + debug!(" r='{=u8:#04x}H' ({:03}D)", read_buf[0], read_buf[0]); + let _ = data_read.push(read_buf[0]).unwrap(); + } + Err(..) => break, // TimeoutError -> Ignore. + } + } + info!("Read successful"); + debug!(" read='{:?}'", data_read[..]); + + Timer::after_secs(3).await; + info!("Changing speed."); + } + + info!("Changing colour."); + } + } +} diff --git a/examples/rp23/src/bin/uart_unidir.rs b/examples/rp23/src/bin/uart_unidir.rs new file mode 100644 index 000000000..988e44a79 --- /dev/null +++ b/examples/rp23/src/bin/uart_unidir.rs @@ -0,0 +1,65 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! Test TX-only and RX-only on two different UARTs. You need to connect GPIO0 to GPIO5 for +//! this to work +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::UART1; +use embassy_rp::uart::{Async, Config, InterruptHandler, UartRx, UartTx}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + UART1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut uart_tx = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, Config::default()); + let uart_rx = UartRx::new(p.UART1, p.PIN_5, Irqs, p.DMA_CH1, Config::default()); + + unwrap!(spawner.spawn(reader(uart_rx))); + + info!("Writing..."); + loop { + let data = [1u8, 2, 3, 4, 5, 6, 7, 8]; + info!("TX {:?}", data); + uart_tx.write(&data).await.unwrap(); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, UART1, Async>) { + info!("Reading..."); + loop { + // read a total of 4 transmissions (32 / 8) and then print the result + let mut buf = [0; 32]; + rx.read(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/examples/rp23/src/bin/usb_webusb.rs b/examples/rp23/src/bin/usb_webusb.rs new file mode 100644 index 000000000..3ade2226b --- /dev/null +++ b/examples/rp23/src/bin/usb_webusb.rs @@ -0,0 +1,170 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates a WebUSB capable device that echoes data back to the host. +//! +//! To test this in the browser (ideally host this on localhost:8080, to test the landing page +//! feature): +//! ```js +//! (async () => { +//! const device = await navigator.usb.requestDevice({ filters: [{ vendorId: 0xf569 }] }); +//! await device.open(); +//! await device.claimInterface(1); +//! device.transferIn(1, 64).then(data => console.log(data)); +//! await device.transferOut(1, new Uint8Array([1,2,3])); +//! })(); +//! ``` + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler}; +use embassy_usb::class::web_usb::{Config as WebUsbConfig, State, Url, WebUsb}; +use embassy_usb::driver::{Driver, Endpoint, EndpointIn, EndpointOut}; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = UsbDriver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xf569, 0x0001); + config.manufacturer = Some("Embassy"); + config.product = Some("WebUSB example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xff; + config.device_sub_class = 0x00; + config.device_protocol = 0x00; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut msos_descriptor = [0; 256]; + + let webusb_config = WebUsbConfig { + max_packet_size: 64, + vendor_code: 1, + // If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device. Suggest the user to navigate to this URL when the device is connected. + landing_url: Some(Url::new("http://localhost:8080")), + }; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Create classes on the builder (WebUSB just needs some setup, but doesn't return anything) + WebUsb::configure(&mut builder, &mut state, &webusb_config); + // Create some USB bulk endpoints for testing. + let mut endpoints = WebEndpoints::new(&mut builder, &webusb_config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do some WebUSB transfers. + let webusb_fut = async { + loop { + endpoints.wait_connected().await; + info!("Connected"); + endpoints.echo().await; + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, webusb_fut).await; +} + +struct WebEndpoints<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> WebEndpoints<'d, D> { + fn new(builder: &mut Builder<'d, D>, config: &'d WebUsbConfig<'d>) -> Self { + let mut func = builder.function(0xff, 0x00, 0x00); + let mut iface = func.interface(); + let mut alt = iface.alt_setting(0xff, 0x00, 0x00, None); + + let write_ep = alt.endpoint_bulk_in(config.max_packet_size); + let read_ep = alt.endpoint_bulk_out(config.max_packet_size); + + WebEndpoints { write_ep, read_ep } + } + + // Wait until the device's endpoints are enabled. + async fn wait_connected(&mut self) { + self.read_ep.wait_enabled().await + } + + // Echo data back to the host. + async fn echo(&mut self) { + let mut buf = [0; 64]; + loop { + let n = self.read_ep.read(&mut buf).await.unwrap(); + let data = &buf[..n]; + info!("Data read: {:x}", data); + self.write_ep.write(data).await.unwrap(); + } + } +} diff --git a/examples/rp23/src/bin/watchdog.rs b/examples/rp23/src/bin/watchdog.rs new file mode 100644 index 000000000..a901c1164 --- /dev/null +++ b/examples/rp23/src/bin/watchdog.rs @@ -0,0 +1,66 @@ +//! This example shows how to use Watchdog in the RP2040 chip. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs or connect external LED and resistor. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio; +use embassy_rp::watchdog::*; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello world!"); + + let mut watchdog = Watchdog::new(p.WATCHDOG); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led.set_high(); + Timer::after_secs(2).await; + + // Set to watchdog to reset if it's not fed within 1.05 seconds, and start it + watchdog.start(Duration::from_millis(1_050)); + info!("Started the watchdog timer"); + + // Blink once a second for 5 seconds, feed the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led.set_low(); + Timer::after_millis(500).await; + led.set_high(); + Timer::after_millis(500).await; + info!("Feeding watchdog"); + watchdog.feed(); + } + + info!("Stopped feeding, device will reset in 1.05 seconds"); + // Blink 10 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds. + loop { + led.set_low(); + Timer::after_millis(100).await; + led.set_high(); + Timer::after_millis(100).await; + } +} diff --git a/examples/rp23/src/bin/zerocopy.rs b/examples/rp23/src/bin/zerocopy.rs new file mode 100644 index 000000000..86fca6f12 --- /dev/null +++ b/examples/rp23/src/bin/zerocopy.rs @@ -0,0 +1,109 @@ +//! This example shows how to use `zerocopy_channel` from `embassy_sync` for +//! sending large values between two tasks without copying. +//! The example also shows how to use the RP2040 ADC with DMA. +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU16, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{self, Adc, Async, Config, InterruptHandler}; +use embassy_rp::bind_interrupts; +use embassy_rp::block::ImageDef; +use embassy_rp::gpio::Pull; +use embassy_rp::peripherals::DMA_CH0; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::zerocopy_channel::{Channel, Receiver, Sender}; +use embassy_time::{Duration, Ticker, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".start_block"] +#[used] +pub static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); + +// Program metadata for `picotool info` +#[link_section = ".bi_entries"] +#[used] +pub static PICOTOOL_ENTRIES: [embassy_rp::binary_info::EntryAddr; 4] = [ + embassy_rp::binary_info::rp_program_name!(c"example"), + embassy_rp::binary_info::rp_cargo_version!(), + embassy_rp::binary_info::rp_program_description!(c"Blinky"), + embassy_rp::binary_info::rp_program_build_attribute!(), +]; + +type SampleBuffer = [u16; 512]; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +const BLOCK_SIZE: usize = 512; +const NUM_BLOCKS: usize = 2; +static MAX: AtomicU16 = AtomicU16::new(0); + +struct AdcParts { + adc: Adc<'static, Async>, + pin: adc::Channel<'static>, + dma: DMA_CH0, +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Here we go!"); + + let adc_parts = AdcParts { + adc: Adc::new(p.ADC, Irqs, Config::default()), + pin: adc::Channel::new_pin(p.PIN_29, Pull::None), + dma: p.DMA_CH0, + }; + + static BUF: StaticCell<[SampleBuffer; NUM_BLOCKS]> = StaticCell::new(); + let buf = BUF.init([[0; BLOCK_SIZE]; NUM_BLOCKS]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(Channel::new(buf)); + let (sender, receiver) = channel.split(); + + spawner.must_spawn(consumer(receiver)); + spawner.must_spawn(producer(sender, adc_parts)); + + let mut ticker = Ticker::every(Duration::from_secs(1)); + loop { + ticker.next().await; + let max = MAX.load(Ordering::Relaxed); + info!("latest block's max value: {:?}", max); + } +} + +#[embassy_executor::task] +async fn producer(mut sender: Sender<'static, NoopRawMutex, SampleBuffer>, mut adc: AdcParts) { + loop { + // Obtain a free buffer from the channel + let buf = sender.send().await; + + // Fill it with data + adc.adc.read_many(&mut adc.pin, buf, 1, &mut adc.dma).await.unwrap(); + + // Notify the channel that the buffer is now ready to be received + sender.send_done(); + } +} + +#[embassy_executor::task] +async fn consumer(mut receiver: Receiver<'static, NoopRawMutex, SampleBuffer>) { + loop { + // Receive a buffer from the channel + let buf = receiver.receive().await; + + // Simulate using the data, while the producer is filling up the next buffer + Timer::after_micros(1000).await; + let max = buf.iter().max().unwrap(); + MAX.store(*max, Ordering::Relaxed); + + // Notify the channel that the buffer is now ready to be reused + receiver.receive_done(); + } +} diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index 58ea894f3..87491b1d2 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -6,8 +6,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["log", "std", ] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-std", "executor-thread", "log", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "std", ] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features=[ "std", "log", "medium-ethernet", "medium-ip", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6"] } embassy-net-tuntap = { version = "0.1.0", path = "../../embassy-net-tuntap" } embassy-net-ppp = { version = "0.1.0", path = "../../embassy-net-ppp", features = ["log"]} diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 59813d8cb..cefa5448c 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -1,7 +1,7 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use embassy_time::Duration; use embedded_io_async::Write; @@ -22,8 +22,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -50,17 +50,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<3>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_buffer = [0; 4096]; diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs index 3b6a3de37..a42c5dbb7 100644 --- a/examples/std/src/bin/net_dns.rs +++ b/examples/std/src/bin/net_dns.rs @@ -1,7 +1,7 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::dns::DnsQueryType; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use heapless::Vec; use log::*; @@ -20,8 +20,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -49,17 +49,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack: &Stack<_> = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<3>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); let host = "example.com"; info!("querying host {:?}...", host); diff --git a/examples/std/src/bin/net_ppp.rs b/examples/std/src/bin/net_ppp.rs index 9ec0ea91f..7d0f1327f 100644 --- a/examples/std/src/bin/net_ppp.rs +++ b/examples/std/src/bin/net_ppp.rs @@ -37,16 +37,12 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, embassy_net_ppp::Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::task] -async fn ppp_task( - stack: &'static Stack>, - mut runner: Runner<'static>, - port: SerialPort, -) -> ! { +async fn ppp_task(stack: Stack<'static>, mut runner: Runner<'static>, port: SerialPort) -> ! { let port = Async::new(port).unwrap(); let port = BufReader::new(port); let port = adapter::FromFutures::new(port); @@ -97,17 +93,16 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, net_runner) = embassy_net::new( device, Config::default(), // don't configure IP yet - RESOURCES.init(StackResources::<3>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(net_runner)).unwrap(); spawner.spawn(ppp_task(stack, runner, port)).unwrap(); // Then we can use it! diff --git a/examples/std/src/bin/net_udp.rs b/examples/std/src/bin/net_udp.rs index bee91990d..02d4d3efb 100644 --- a/examples/std/src/bin/net_udp.rs +++ b/examples/std/src/bin/net_udp.rs @@ -1,7 +1,7 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::udp::{PacketMetadata, UdpSocket}; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use heapless::Vec; use log::*; @@ -20,8 +20,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[embassy_executor::task] @@ -48,17 +48,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<3>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_meta = [PacketMetadata::EMPTY; 16]; diff --git a/examples/std/src/bin/tcp_accept.rs b/examples/std/src/bin/tcp_accept.rs index e8b6eaa6c..5d36b739d 100644 --- a/examples/std/src/bin/tcp_accept.rs +++ b/examples/std/src/bin/tcp_accept.rs @@ -3,7 +3,7 @@ use core::fmt::Write as _; use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, StackResources}; use embassy_net_tuntap::TunTapDevice; use embassy_time::{Duration, Timer}; use embedded_io_async::Write as _; @@ -24,8 +24,8 @@ struct Opts { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, TunTapDevice>) -> ! { + runner.run().await } #[derive(Default)] @@ -62,17 +62,11 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<3>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - spawner.spawn(net_task(stack)).unwrap(); + spawner.spawn(net_task(runner)).unwrap(); // Then we can use it! let mut rx_buffer = [0; 4096]; diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml index 331046a80..9102467eb 100644 --- a/examples/stm32c0/Cargo.toml +++ b/examples/stm32c0/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32c031c6 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 15fb55ca7..724efdaff 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -13,8 +13,8 @@ defmt = "0.3" defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } static_cell = "2" portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 38b615795..0084651a3 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32f103c8 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any" ] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index ec9b54920..60eb0eb93 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32f207zg to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index 62c0fed16..7fda410d9 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32f303ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32f334/Cargo.toml b/examples/stm32f334/Cargo.toml index 84c44a7b7..1cc0a97da 100644 --- a/examples/stm32f334/Cargo.toml +++ b/examples/stm32f334/Cargo.toml @@ -6,10 +6,10 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32f334r8", "unstable-pac", "memory-x", "time-driver-any", "exti"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index dbfaecb5e..b85361596 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32f429zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt" ] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt" ] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32f4/src/bin/eth.rs b/examples/stm32f4/src/bin/eth.rs index 648c45bbd..baed96449 100644 --- a/examples/stm32f4/src/bin/eth.rs +++ b/examples/stm32f4/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -24,8 +24,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -88,17 +88,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -110,7 +104,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 4096]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32f4/src/bin/eth_w5500.rs b/examples/stm32f4/src/bin/eth_w5500.rs index 3c770a873..6e6bef08c 100644 --- a/examples/stm32f4/src/bin/eth_w5500.rs +++ b/examples/stm32f4/src/bin/eth_w5500.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_net_wiznet::chip::W5500; use embassy_net_wiznet::{Device, Runner, State}; use embassy_stm32::exti::ExtiInput; @@ -31,8 +31,8 @@ async fn ethernet_task(runner: Runner<'static, W5500, EthernetSPI, ExtiInput<'st } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -92,17 +92,11 @@ async fn main(spawner: Spawner) -> ! { // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); - static STACK: StaticCell> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs index b398c35da..a9504ec04 100644 --- a/examples/stm32f4/src/bin/usb_ethernet.rs +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::rng::{self, Rng}; use embassy_stm32::time::Hertz; use embassy_stm32::usb::Driver; @@ -31,8 +31,8 @@ async fn usb_ncm_task(class: Runner<'static, UsbDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } bind_interrupts!(struct Irqs { @@ -144,16 +144,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/stm32f469/Cargo.toml b/examples/stm32f469/Cargo.toml index 634f13f4e..6a5bd0b29 100644 --- a/examples/stm32f469/Cargo.toml +++ b/examples/stm32f469/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0" [dependencies] # Specific examples only for stm32f469 embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f469ni", "unstable-pac", "memory-x", "time-driver-any", "exti", "chrono"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 7fc7d6f42..8c591ebd2 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -8,11 +8,11 @@ license = "MIT OR Apache-2.0" # Change stm32f777zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embedded-io-async = { version = "0.6.1" } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 41e2a6061..1f1eadf37 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -89,17 +89,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -111,7 +105,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 4096]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index b9d710ca7..a50074ce0 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32g0b1re to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g0b1re", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 8837ca52e..2768147a1 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32g491re to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } usbd-hid = "0.8.1" diff --git a/examples/stm32g4/src/bin/adc_differential.rs b/examples/stm32g4/src/bin/adc_differential.rs new file mode 100644 index 000000000..78d071d45 --- /dev/null +++ b/examples/stm32g4/src/bin/adc_differential.rs @@ -0,0 +1,47 @@ +//! adc differential mode example +//! +//! This example uses adc1 in differential mode +//! p:pa0 n:pa1 + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let mut p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES247_5); + adc.set_differential(&mut p.PA0, true); //p:pa0,n:pa1 + + // can also use + // adc.set_differential_channel(1, true); + info!("adc initialized"); + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: {}", measured); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32g4/src/bin/adc_oversampling.rs b/examples/stm32g4/src/bin/adc_oversampling.rs new file mode 100644 index 000000000..d31eb20f8 --- /dev/null +++ b/examples/stm32g4/src/bin/adc_oversampling.rs @@ -0,0 +1,57 @@ +//! adc oversampling example +//! +//! This example uses adc oversampling to achieve 16bit data + +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::vals::{Rovsm, Trovs}; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::Config; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } + let mut p = embassy_stm32::init(config); + + let mut adc = Adc::new(p.ADC1); + adc.set_sample_time(SampleTime::CYCLES6_5); + // From https://www.st.com/resource/en/reference_manual/rm0440-stm32g4-series-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // page652 Oversampler + // Table 172. Maximum output results vs N and M. Grayed values indicates truncation + // 0x00 oversampling ratio X2 + // 0x01 oversampling ratio X4 + // 0x02 oversampling ratio X8 + // 0x03 oversampling ratio X16 + // 0x04 oversampling ratio X32 + // 0x05 oversampling ratio X64 + // 0x06 oversampling ratio X128 + // 0x07 oversampling ratio X256 + adc.set_oversampling_ratio(0x03); // ratio X3 + adc.set_oversampling_shift(0b0000); // no shift + adc.enable_regular_oversampling_mode(Rovsm::RESUMED, Trovs::AUTOMATIC, true); + + loop { + let measured = adc.blocking_read(&mut p.PA0); + info!("data: 0x{:X}", measured); //max 0xFFF0 -> 65520 + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32g4/src/bin/usb_c_pd.rs b/examples/stm32g4/src/bin/usb_c_pd.rs index 7caea634f..2e87d3931 100644 --- a/examples/stm32g4/src/bin/usb_c_pd.rs +++ b/examples/stm32g4/src/bin/usb_c_pd.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); - let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4); + let mut ucpd = Ucpd::new(p.UCPD1, Irqs {}, p.PB6, p.PB4, Default::default()); ucpd.cc_phy().set_pull(CcPull::Sink); info!("Waiting for USB connection..."); diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml index 59d3a9759..30b1d2be9 100644 --- a/examples/stm32h5/Cargo.toml +++ b/examples/stm32h5/Cargo.toml @@ -8,10 +8,10 @@ license = "MIT OR Apache-2.0" # Change stm32h563zi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h563zi", "memory-x", "time-driver-any", "exti", "unstable-pac", "low-power"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32h5/src/bin/eth.rs b/examples/stm32h5/src/bin/eth.rs index 2370656e6..eee1632f5 100644 --- a/examples/stm32h5/src/bin/eth.rs +++ b/examples/stm32h5/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -28,8 +28,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -92,17 +92,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -114,7 +108,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 1024]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 78343b74f..13fce7dc7 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -8,11 +8,11 @@ license = "MIT OR Apache-2.0" # Change stm32h743bi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h743bi", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs index 7c7964ecd..ec3f2c000 100644 --- a/examples/stm32h7/src/bin/eth.rs +++ b/examples/stm32h7/src/bin/eth.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_net::{Ipv4Address, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -24,8 +24,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -91,17 +91,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<3>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -113,7 +107,7 @@ async fn main(spawner: Spawner) -> ! { let mut tx_buffer = [0; 1024]; loop { - let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 0639fb99f..24983ca85 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -91,17 +91,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<3>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -109,7 +103,7 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); - let client = TcpClient::new(&stack, &state); + let client = TcpClient::new(stack, &state); loop { // You need to start a server on the host machine, for example: `nc -l 8000` diff --git a/examples/stm32h7/src/bin/eth_client_mii.rs b/examples/stm32h7/src/bin/eth_client_mii.rs index 9a52e8d3b..768d85993 100644 --- a/examples/stm32h7/src/bin/eth_client_mii.rs +++ b/examples/stm32h7/src/bin/eth_client_mii.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -97,17 +97,11 @@ async fn main(spawner: Spawner) -> ! { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<3>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // Ensure DHCP configuration is up before trying connect stack.wait_config_up().await; @@ -115,7 +109,7 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); - let client = TcpClient::new(&stack, &state); + let client = TcpClient::new(stack, &state); loop { // You need to start a server on the host machine, for example: `nc -l 8000` diff --git a/examples/stm32h735/Cargo.toml b/examples/stm32h735/Cargo.toml index fc21cc894..93e9575b6 100644 --- a/examples/stm32h735/Cargo.toml +++ b/examples/stm32h735/Cargo.toml @@ -7,9 +7,9 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h735ig", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32h755cm4/.cargo/config.toml b/examples/stm32h755cm4/.cargo/config.toml new file mode 100644 index 000000000..193e6bbc3 --- /dev/null +++ b/examples/stm32h755cm4/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H755ZITx --catch-hardfault --always-print-stacktrace' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h755cm4/Cargo.toml b/examples/stm32h755cm4/Cargo.toml new file mode 100644 index 000000000..7a42fbdaa --- /dev/null +++ b/examples/stm32h755cm4/Cargo.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h755cm4-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h755zi-cm4 to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm4", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = { version = "0.7.1" } +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h755cm4/build.rs b/examples/stm32h755cm4/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h755cm4/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h755cm4/memory.x b/examples/stm32h755cm4/memory.x new file mode 100644 index 000000000..7d60354e3 --- /dev/null +++ b/examples/stm32h755cm4/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08100000, LENGTH = 1024K /* BANK_2 */ + RAM : ORIGIN = 0x10000000, LENGTH = 128K /* SRAM1 */ + RAM_D3 : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 */ +} + +SECTIONS +{ + .ram_d3 : + { + *(.ram_d3.shared_data) + *(.ram_d3) + } > RAM_D3 +} \ No newline at end of file diff --git a/examples/stm32h755cm4/src/bin/blinky.rs b/examples/stm32h755cm4/src/bin/blinky.rs new file mode 100644 index 000000000..b5c547839 --- /dev/null +++ b/examples/stm32h755cm4/src/bin/blinky.rs @@ -0,0 +1,32 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".ram_d3.shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init_secondary(&SHARED_DATA); + info!("Hello World!"); + + let mut led = Output::new(p.PE1, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(250).await; + + info!("low"); + led.set_low(); + Timer::after_millis(250).await; + } +} diff --git a/examples/stm32h755cm7/.cargo/config.toml b/examples/stm32h755cm7/.cargo/config.toml new file mode 100644 index 000000000..193e6bbc3 --- /dev/null +++ b/examples/stm32h755cm7/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip STM32H755ZITx --catch-hardfault --always-print-stacktrace' + +[build] +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h755cm7/Cargo.toml b/examples/stm32h755cm7/Cargo.toml new file mode 100644 index 000000000..4f0f69c3f --- /dev/null +++ b/examples/stm32h755cm7/Cargo.toml @@ -0,0 +1,75 @@ +[package] +edition = "2021" +name = "embassy-stm32h755cm7-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +# Change stm32h743bi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h755zi-cm7", "time-driver-tim3", "exti", "memory-x", "unstable-pac", "chrono"] } +embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-nal-async = { version = "0.7.1" } +embedded-io-async = { version = "0.6.1" } +panic-probe = { version = "0.3", features = ["print-defmt"] } +heapless = { version = "0.8", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.3.0" +embedded-storage = "0.3.1" +static_cell = "2" +chrono = { version = "^0.4", default-features = false } +grounded = "0.2.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h755cm7/build.rs b/examples/stm32h755cm7/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/stm32h755cm7/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h755cm7/memory.x b/examples/stm32h755cm7/memory.x new file mode 100644 index 000000000..ef884796a --- /dev/null +++ b/examples/stm32h755cm7/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 1024K /* BANK_1 */ + RAM : ORIGIN = 0x24000000, LENGTH = 512K /* AXIRAM */ + RAM_D3 : ORIGIN = 0x38000000, LENGTH = 64K /* SRAM4 */ +} + +SECTIONS +{ + .ram_d3 : + { + *(.ram_d3.shared_data) + *(.ram_d3) + } > RAM_D3 +} \ No newline at end of file diff --git a/examples/stm32h755cm7/src/bin/blinky.rs b/examples/stm32h755cm7/src/bin/blinky.rs new file mode 100644 index 000000000..94d2226c0 --- /dev/null +++ b/examples/stm32h755cm7/src/bin/blinky.rs @@ -0,0 +1,54 @@ +#![no_std] +#![no_main] + +use core::mem::MaybeUninit; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[link_section = ".ram_d3.shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV8), // 100mhz + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.supply_config = SupplyConfig::DirectSMPS; + } + let p = embassy_stm32::init_primary(config, &SHARED_DATA); + info!("Hello World!"); + + let mut led = Output::new(p.PB14, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(500).await; + + info!("low"); + led.set_low(); + Timer::after_millis(500).await; + } +} diff --git a/examples/stm32h7rs/Cargo.toml b/examples/stm32h7rs/Cargo.toml index 13d33320f..f97dfd722 100644 --- a/examples/stm32h7rs/Cargo.toml +++ b/examples/stm32h7rs/Cargo.toml @@ -8,10 +8,10 @@ license = "MIT OR Apache-2.0" # Change stm32h743bi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32h7s3l8", "time-driver-tim2", "exti", "memory-x", "unstable-pac", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "proto-ipv6", "dns"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 5b0519ac4..2577f19e0 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -8,8 +8,8 @@ license = "MIT OR Apache-2.0" # Change stm32l072cz to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "unstable-pac", "time-driver-any", "exti", "memory-x"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index 9e4ceac6a..062044f32 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -6,10 +6,10 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index de2b2bd4d..c5478b17b 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -8,10 +8,10 @@ license = "MIT OR Apache-2.0" # Change stm32l4s5vi to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l4s5qi", "memory-x", "time-driver-any", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768", ] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net-adin1110 = { version = "0.2.0", path = "../../embassy-net-adin1110" } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs index 33149144c..be4270ada 100644 --- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -206,17 +206,11 @@ async fn main(spawner: Spawner) { }; // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - ip_cfg, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, ip_cfg, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); let cfg = wait_for_config(stack).await; let local_addr = cfg.address.address(); @@ -279,7 +273,7 @@ async fn main(spawner: Spawner) { } } -async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { +async fn wait_for_config(stack: Stack<'static>) -> embassy_net::StaticConfigV4 { loop { if let Some(config) = stack.config_v4() { return config; @@ -328,8 +322,8 @@ async fn ethernet_task(runner: Runner<'static, SpeSpiCs, SpeInt, SpeRst>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } // same panicking *behavior* as `panic-probe` but doesn't print a panic message diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index e32714cc6..16c184de2 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32l552ze to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "memory-x", "low-power"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } usbd-hid = "0.8.1" diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index 7f73fd677..095d50c73 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::rng::Rng; use embassy_stm32::usb::Driver; use embassy_stm32::{bind_interrupts, peripherals, rng, usb, Config}; @@ -36,8 +36,8 @@ async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static, MTU>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -121,16 +121,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); - static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + static RESOURCES: StaticCell> = StaticCell::new(); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); // And now we can use it! diff --git a/examples/stm32u0/Cargo.toml b/examples/stm32u0/Cargo.toml index afeb4dc34..2e890cdb5 100644 --- a/examples/stm32u0/Cargo.toml +++ b/examples/stm32u0/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32u083rc to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "time-driver-any", "stm32u083rc", "memory-x", "unstable-pac", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", default-features = false, features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index d0134b972..20d64c6f7 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32u585ai to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-usb = { version = "0.2.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-usb = { version = "0.3.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index c3a11b14b..1e1a0efe2 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -9,8 +9,8 @@ license = "MIT OR Apache-2.0" embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } defmt = "0.3" diff --git a/examples/stm32wba/Cargo.toml b/examples/stm32wba/Cargo.toml index dc788e15b..401281c0b 100644 --- a/examples/stm32wba/Cargo.toml +++ b/examples/stm32wba/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0" [dependencies] embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "stm32wba52cg", "time-driver-any", "memory-x", "exti"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "udp", "proto-ipv6", "medium-ieee802154", ], optional=true } defmt = "0.3" diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 3ea7d0cbe..46af5218c 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -8,9 +8,9 @@ license = "MIT OR Apache-2.0" # Change stm32wl55jc-cm4 to your chip name, if necessary. embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal" } defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32wl/memory.x b/examples/stm32wl/memory.x new file mode 100644 index 000000000..4590867a8 --- /dev/null +++ b/examples/stm32wl/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 256K + SHARED_RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128 + RAM (rwx) : ORIGIN = 0x20000080, LENGTH = 64K - 128 +} + +SECTIONS +{ + .shared_data : + { + *(.shared_data) + } > SHARED_RAM +} diff --git a/examples/stm32wl/src/bin/blinky.rs b/examples/stm32wl/src/bin/blinky.rs index 347bd093f..ce7d0ec58 100644 --- a/examples/stm32wl/src/bin/blinky.rs +++ b/examples/stm32wl/src/bin/blinky.rs @@ -1,15 +1,21 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + use defmt::*; use embassy_executor::Spawner; use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::SharedData; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); info!("Hello World!"); let mut led = Output::new(p.PB15, Level::High, Speed::Low); diff --git a/examples/stm32wl/src/bin/button.rs b/examples/stm32wl/src/bin/button.rs index eccd211e2..8b5204479 100644 --- a/examples/stm32wl/src/bin/button.rs +++ b/examples/stm32wl/src/bin/button.rs @@ -1,16 +1,22 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + use cortex_m_rt::entry; use defmt::*; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[entry] fn main() -> ! { info!("Hello World!"); - let p = embassy_stm32::init(Default::default()); + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); let button = Input::new(p.PA0, Pull::Up); let mut led1 = Output::new(p.PB15, Level::High, Speed::Low); diff --git a/examples/stm32wl/src/bin/button_exti.rs b/examples/stm32wl/src/bin/button_exti.rs index 27d5330bd..8dd1a6a5e 100644 --- a/examples/stm32wl/src/bin/button_exti.rs +++ b/examples/stm32wl/src/bin/button_exti.rs @@ -1,15 +1,21 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::gpio::Pull; +use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); info!("Hello World!"); let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up); diff --git a/examples/stm32wl/src/bin/flash.rs b/examples/stm32wl/src/bin/flash.rs index 0b7417c01..147f5d293 100644 --- a/examples/stm32wl/src/bin/flash.rs +++ b/examples/stm32wl/src/bin/flash.rs @@ -1,14 +1,20 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_stm32::flash::Flash; +use embassy_stm32::SharedData; use {defmt_rtt as _, panic_probe as _}; +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(Default::default()); + let p = embassy_stm32::init_primary(Default::default(), &SHARED_DATA); info!("Hello Flash!"); const ADDR: u32 = 0x36000; diff --git a/examples/stm32wl/src/bin/random.rs b/examples/stm32wl/src/bin/random.rs index 8e9fe02b2..df2ed0054 100644 --- a/examples/stm32wl/src/bin/random.rs +++ b/examples/stm32wl/src/bin/random.rs @@ -1,17 +1,22 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + use defmt::*; use embassy_executor::Spawner; use embassy_stm32::rng::{self, Rng}; use embassy_stm32::time::Hertz; -use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::{bind_interrupts, peripherals, SharedData}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs{ RNG => rng::InterruptHandler; }); +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = embassy_stm32::Config::default(); @@ -32,7 +37,7 @@ async fn main(_spawner: Spawner) { divr: Some(PllRDiv::DIV2), // sysclk 48Mhz clock (32 / 2 * 6 / 2) }); } - let p = embassy_stm32::init(config); + let p = embassy_stm32::init_primary(config, &SHARED_DATA); info!("Hello World!"); diff --git a/examples/stm32wl/src/bin/rtc.rs b/examples/stm32wl/src/bin/rtc.rs index cf7d6d220..69a9ddc4c 100644 --- a/examples/stm32wl/src/bin/rtc.rs +++ b/examples/stm32wl/src/bin/rtc.rs @@ -1,15 +1,20 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + use chrono::{NaiveDate, NaiveDateTime}; use defmt::*; use embassy_executor::Spawner; use embassy_stm32::rtc::{Rtc, RtcConfig}; use embassy_stm32::time::Hertz; -use embassy_stm32::Config; +use embassy_stm32::{Config, SharedData}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); @@ -31,7 +36,7 @@ async fn main(_spawner: Spawner) { divr: Some(PllRDiv::DIV2), // sysclk 48Mhz clock (32 / 2 * 6 / 2) }); } - let p = embassy_stm32::init(config); + let p = embassy_stm32::init_primary(config, &SHARED_DATA); info!("Hello World!"); let now = NaiveDate::from_ymd_opt(2020, 5, 15) diff --git a/examples/stm32wl/src/bin/uart_async.rs b/examples/stm32wl/src/bin/uart_async.rs index 3637243a0..ece9b9201 100644 --- a/examples/stm32wl/src/bin/uart_async.rs +++ b/examples/stm32wl/src/bin/uart_async.rs @@ -1,10 +1,12 @@ #![no_std] #![no_main] +use core::mem::MaybeUninit; + use defmt::*; use embassy_executor::Spawner; use embassy_stm32::usart::{Config, InterruptHandler, Uart}; -use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_stm32::{bind_interrupts, peripherals, SharedData}; use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs{ @@ -12,6 +14,9 @@ bind_interrupts!(struct Irqs{ LPUART1 => InterruptHandler; }); +#[link_section = ".shared_data"] +static SHARED_DATA: MaybeUninit = MaybeUninit::uninit(); + /* Pass Incoming data from LPUART1 to USART1 Example is written for the LoRa-E5 mini v1.0, @@ -21,7 +26,7 @@ but can be surely changed for your needs. async fn main(_spawner: Spawner) { let mut config = embassy_stm32::Config::default(); config.rcc.sys = embassy_stm32::rcc::Sysclk::HSE; - let p = embassy_stm32::init(config); + let p = embassy_stm32::init_primary(config, &SHARED_DATA); defmt::info!("Starting system"); diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index 9d7acc175..75de079b7 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -9,14 +9,13 @@ crate-type = ["cdylib"] [dependencies] embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["log", "wasm", ] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["log", "wasm", ] } wasm-logger = "0.2.0" wasm-bindgen = "0.2" web-sys = { version = "0.3", features = ["Document", "Element", "HtmlElement", "Node", "Window" ] } log = "0.4.11" -critical-section = { version = "1.1", features = ["std"] } [profile.release] debug = 2 diff --git a/rust-toolchain-nightly.toml b/rust-toolchain-nightly.toml index dfa231344..0b10d7194 100644 --- a/rust-toolchain-nightly.toml +++ b/rust-toolchain-nightly.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "nightly-2024-07-16" +channel = "nightly-2024-09-06" components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ] targets = [ "thumbv7em-none-eabi", diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 037fc5c6a..80acb3b5e 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,5 @@ [toolchain] -channel = "1.79" +channel = "1.81" components = [ "rust-src", "rustfmt", "llvm-tools" ] targets = [ "thumbv7em-none-eabi", diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml index c714e8f7e..f666634d5 100644 --- a/tests/nrf/Cargo.toml +++ b/tests/nrf/Cargo.toml @@ -9,9 +9,9 @@ teleprobe-meta = "1" embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt", ] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-16384", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.2.0", path = "../../embassy-nrf", features = ["defmt", "time-driver-rtc1", "gpiote", "unstable-pac"] } embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", ] } embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } diff --git a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs index 5f4220b1e..ed58627f1 100644 --- a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs +++ b/tests/nrf/src/bin/ethernet_enc28j60_perf.rs @@ -6,7 +6,7 @@ teleprobe_meta::timeout!(120); use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_enc28j60::Enc28j60; use embassy_nrf::gpio::{Level, Output, OutputDrive}; use embassy_nrf::rng::Rng; @@ -25,8 +25,8 @@ bind_interrupts!(struct Irqs { type MyDriver = Enc28j60, Output<'static>, Delay>, Output<'static>>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -65,16 +65,10 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs index a6c93c8a6..34fb8103b 100644 --- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs +++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs @@ -6,7 +6,7 @@ teleprobe_meta::timeout!(120); use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; @@ -40,8 +40,8 @@ async fn wifi_task( type MyDriver = hosted::NetDriver<'static>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, MyDriver>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -86,16 +86,15 @@ async fn main(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, Config::dhcpv4(Default::default()), - RESOURCES.init(StackResources::<2>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/perf-client/Cargo.toml b/tests/perf-client/Cargo.toml index 6ecc2d5e1..eb2a33a30 100644 --- a/tests/perf-client/Cargo.toml +++ b/tests/perf-client/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", ] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3.0" diff --git a/tests/perf-client/src/lib.rs b/tests/perf-client/src/lib.rs index 54762379a..4bd9e5674 100644 --- a/tests/perf-client/src/lib.rs +++ b/tests/perf-client/src/lib.rs @@ -2,7 +2,6 @@ use defmt::{assert, *}; use embassy_futures::join::join; -use embassy_net::driver::Driver; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack}; use embassy_time::{with_timeout, Duration, Timer}; @@ -13,7 +12,7 @@ pub struct Expected { pub updown_kbps: usize, } -pub async fn run(stack: &Stack, expected: Expected) { +pub async fn run(stack: Stack<'_>, expected: Expected) { info!("Waiting for DHCP up..."); while stack.config_v4().is_none() { Timer::after_millis(100).await; @@ -38,7 +37,7 @@ const DOWNLOAD_PORT: u16 = 4321; const UPLOAD_PORT: u16 = 4322; const UPLOAD_DOWNLOAD_PORT: u16 = 4323; -async fn test_download(stack: &Stack) -> usize { +async fn test_download(stack: Stack<'_>) -> usize { info!("Testing download..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; @@ -78,7 +77,7 @@ async fn test_download(stack: &Stack) -> usize { kbps } -async fn test_upload(stack: &Stack) -> usize { +async fn test_upload(stack: Stack<'_>) -> usize { info!("Testing upload..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; @@ -118,7 +117,7 @@ async fn test_upload(stack: &Stack) -> usize { kbps } -async fn test_upload_download(stack: &Stack) -> usize { +async fn test_upload_download(stack: Stack<'_>) -> usize { info!("Testing upload+download..."); let mut rx_buffer = [0; RX_BUFFER_SIZE]; diff --git a/tests/riscv32/Cargo.toml b/tests/riscv32/Cargo.toml index 4f1d3e5c6..ae2b0e180 100644 --- a/tests/riscv32/Cargo.toml +++ b/tests/riscv32/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0" [dependencies] critical-section = { version = "1.1.1", features = ["restore-state-bool"] } embassy-sync = { version = "0.6.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time" } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-riscv32", "executor-thread"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time" } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } riscv-rt = "0.12.2" diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 45050ee0e..12f1ec3ce 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -8,13 +8,13 @@ license = "MIT OR Apache-2.0" teleprobe-meta = "1.1" embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", ] } -embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", ] } +embassy-rp = { version = "0.2.0", path = "../../embassy-rp", features = [ "defmt", "unstable-pac", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram", "rp2040"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } -embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal/"} +embassy-embedded-hal = { version = "0.2.0", path = "../../embassy-embedded-hal/"} cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } perf-client = { path = "../perf-client" } diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs index 53c84e711..30e4afb07 100644 --- a/tests/rp/src/bin/cyw43-perf.rs +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -2,10 +2,11 @@ #![no_main] teleprobe_meta::target!(b"rpi-pico"); +use cyw43::JoinOptions; use cyw43_pio::PioSpi; use defmt::{panic, *}; use embassy_executor::Spawner; -use embassy_net::{Config, Stack, StackResources}; +use embassy_net::{Config, StackResources}; use embassy_rp::gpio::{Level, Output}; use embassy_rp::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; @@ -29,8 +30,8 @@ async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'stati } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, cyw43::NetDriver<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -69,19 +70,21 @@ async fn main(spawner: Spawner) { let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( net_device, Config::dhcpv4(Default::default()), - RESOURCES.init(StackResources::<2>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); - unwrap!(spawner.spawn(net_task(stack))); + unwrap!(spawner.spawn(net_task(runner))); loop { - match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + match control + .join(WIFI_NETWORK, JoinOptions::new(WIFI_PASSWORD.as_bytes())) + .await + { Ok(_) => break, Err(err) => { panic!("join failed with status={}", err.status); diff --git a/tests/rp/src/bin/ethernet_w5100s_perf.rs b/tests/rp/src/bin/ethernet_w5100s_perf.rs index 4b04571bd..ae2adfa55 100644 --- a/tests/rp/src/bin/ethernet_w5100s_perf.rs +++ b/tests/rp/src/bin/ethernet_w5100s_perf.rs @@ -5,7 +5,7 @@ teleprobe_meta::timeout!(120); use defmt::*; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_net_wiznet::chip::W5100S; use embassy_net_wiznet::*; use embassy_rp::clocks::RoscRng; @@ -32,8 +32,8 @@ async fn ethernet_task( } #[embassy_executor::task] -async fn net_task(stack: &'static Stack>) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device<'static>>) -> ! { + runner.run().await } #[embassy_executor::main] @@ -67,17 +67,16 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - static STACK: StaticCell>> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( + let (stack, runner) = embassy_net::new( device, embassy_net::Config::dhcpv4(Default::default()), - RESOURCES.init(StackResources::<2>::new()), + RESOURCES.init(StackResources::new()), seed, - )); + ); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/rp/src/bin/timer.rs b/tests/rp/src/bin/timer.rs new file mode 100644 index 000000000..be9242144 --- /dev/null +++ b/tests/rp/src/bin/timer.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"rpi-pico"); + +use defmt::{assert, *}; +use embassy_executor::Spawner; +use embassy_time::{Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let start = Instant::now(); + Timer::after_millis(100).await; + let end = Instant::now(); + let ms = (end - start).as_millis(); + info!("slept for {} ms", ms); + assert!(ms >= 99); + assert!(ms < 110); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index f94814e70..2eac636e5 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -60,8 +60,8 @@ cm0 = ["portable-atomic/unsafe-assume-single-core"] teleprobe-meta = "1" embassy-sync = { version = "0.6.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } -embassy-time = { version = "0.3.1", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } +embassy-executor = { version = "0.6.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.2", path = "../../embassy-time", features = ["defmt", "tick-hz-131_072", "defmt-timestamp-uptime"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = [ "defmt", "unstable-pac", "memory-x", "time-driver-any"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] } diff --git a/tests/stm32/src/bin/can.rs b/tests/stm32/src/bin/can.rs index ba8a33e34..85a5f8d83 100644 --- a/tests/stm32/src/bin/can.rs +++ b/tests/stm32/src/bin/can.rs @@ -27,7 +27,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); let options = TestOptions { diff --git a/tests/stm32/src/bin/cordic.rs b/tests/stm32/src/bin/cordic.rs index e09226de8..879ad56b6 100644 --- a/tests/stm32/src/bin/cordic.rs +++ b/tests/stm32/src/bin/cordic.rs @@ -29,7 +29,7 @@ const OUTPUT_LENGTH: usize = (INPUT_U32_COUNT - 1) * 2; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let dp = embassy_stm32::init(config()); + let dp = init(); // // use RNG generate random Q1.31 value diff --git a/tests/stm32/src/bin/cryp.rs b/tests/stm32/src/bin/cryp.rs index 60778bdaa..028775ac8 100644 --- a/tests/stm32/src/bin/cryp.rs +++ b/tests/stm32/src/bin/cryp.rs @@ -20,7 +20,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); + let p: embassy_stm32::Peripherals = init(); const PAYLOAD1: &[u8] = b"payload data 1 ;zdfhzdfhS;GKJASBDG;ASKDJBAL,zdfhzdfhzdfhzdfhvljhb,jhbjhb,sdhsdghsdhsfhsghzdfhzdfhzdfhzdfdhsdthsthsdhsgaadfhhgkdgfuoyguoft6783567"; const PAYLOAD2: &[u8] = b"payload data 2 ;SKEzdfhzdfhzbhgvljhb,jhbjhb,sdhsdghsdhsfhsghshsfhshstsdthadfhsdfjhsfgjsfgjxfgjzdhgDFghSDGHjtfjtjszftjzsdtjhstdsdhsdhsdhsdhsdthsthsdhsgfh"; diff --git a/tests/stm32/src/bin/dac.rs b/tests/stm32/src/bin/dac.rs index 86a68c530..88e661525 100644 --- a/tests/stm32/src/bin/dac.rs +++ b/tests/stm32/src/bin/dac.rs @@ -20,7 +20,7 @@ use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { // Initialize the board and obtain a Peripherals instance - let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); + let p: embassy_stm32::Peripherals = init(); let adc = peri!(p, ADC); let dac = peri!(p, DAC); diff --git a/tests/stm32/src/bin/dac_l1.rs b/tests/stm32/src/bin/dac_l1.rs index d5e9c9722..925db617d 100644 --- a/tests/stm32/src/bin/dac_l1.rs +++ b/tests/stm32/src/bin/dac_l1.rs @@ -25,7 +25,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { // Initialize the board and obtain a Peripherals instance - let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); + let p: embassy_stm32::Peripherals = init(); let adc = peri!(p, ADC); let dac = peri!(p, DAC); diff --git a/tests/stm32/src/bin/eth.rs b/tests/stm32/src/bin/eth.rs index 7c02f0354..bf1922dde 100644 --- a/tests/stm32/src/bin/eth.rs +++ b/tests/stm32/src/bin/eth.rs @@ -6,7 +6,7 @@ mod common; use common::*; use embassy_executor::Spawner; -use embassy_net::{Stack, StackResources}; +use embassy_net::StackResources; use embassy_stm32::eth::generic_smi::GenericSMI; use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; @@ -32,13 +32,13 @@ bind_interrupts!(struct Irqs { type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await +async fn net_task(mut runner: embassy_net::Runner<'static, Device>) -> ! { + runner.run().await } #[embassy_executor::main] async fn main(spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); // Generate random seed. @@ -99,17 +99,11 @@ async fn main(spawner: Spawner) { //}); // Init network stack - static STACK: StaticCell> = StaticCell::new(); static RESOURCES: StaticCell> = StaticCell::new(); - let stack = &*STACK.init(Stack::new( - device, - config, - RESOURCES.init(StackResources::<2>::new()), - seed, - )); + let (stack, runner) = embassy_net::new(device, config, RESOURCES.init(StackResources::new()), seed); // Launch network task - unwrap!(spawner.spawn(net_task(&stack))); + unwrap!(spawner.spawn(net_task(runner))); perf_client::run( stack, diff --git a/tests/stm32/src/bin/fdcan.rs b/tests/stm32/src/bin/fdcan.rs index bc2b7edd4..83d7eca85 100644 --- a/tests/stm32/src/bin/fdcan.rs +++ b/tests/stm32/src/bin/fdcan.rs @@ -102,10 +102,10 @@ fn options() -> (Config, TestOptions) { #[embassy_executor::main] async fn main(_spawner: Spawner) { - //let peripherals = embassy_stm32::init(config()); + //let peripherals = init(); let (config, options) = options(); - let peripherals = embassy_stm32::init(config); + let peripherals = init_with_config(config); let mut can = can::CanConfigurator::new(peripherals.FDCAN1, peripherals.PB8, peripherals.PB9, Irqs1); let mut can2 = can::CanConfigurator::new(peripherals.FDCAN2, peripherals.PB12, peripherals.PB13, Irqs2); diff --git a/tests/stm32/src/bin/gpio.rs b/tests/stm32/src/bin/gpio.rs index 1d1018c5c..4a2584b4e 100644 --- a/tests/stm32/src/bin/gpio.rs +++ b/tests/stm32/src/bin/gpio.rs @@ -10,7 +10,7 @@ use embassy_stm32::gpio::{Flex, Input, Level, Output, OutputOpenDrain, Pull, Spe #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); // Arduino pins D0 and D1 diff --git a/tests/stm32/src/bin/hash.rs b/tests/stm32/src/bin/hash.rs index 5f54ea435..bdb3c9a69 100644 --- a/tests/stm32/src/bin/hash.rs +++ b/tests/stm32/src/bin/hash.rs @@ -35,7 +35,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); + let p: embassy_stm32::Peripherals = init(); let mut hw_hasher = Hash::new(p.HASH, NoDma, Irqs); let test_1: &[u8] = b"as;dfhaslfhas;oifvnasd;nifvnhasd;nifvhndlkfghsd;nvfnahssdfgsdafgsasdfasdfasdfasdfasdfghjklmnbvcalskdjghalskdjgfbaslkdjfgbalskdjgbalskdjbdfhsdfhsfghsfghfgh"; diff --git a/tests/stm32/src/bin/rng.rs b/tests/stm32/src/bin/rng.rs index 15ef4fb60..8438353a8 100644 --- a/tests/stm32/src/bin/rng.rs +++ b/tests/stm32/src/bin/rng.rs @@ -41,7 +41,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); + let p: embassy_stm32::Peripherals = init(); let mut rng = Rng::new(p.RNG, Irqs); diff --git a/tests/stm32/src/bin/rtc.rs b/tests/stm32/src/bin/rtc.rs index c04d616ac..5fe98d807 100644 --- a/tests/stm32/src/bin/rtc.rs +++ b/tests/stm32/src/bin/rtc.rs @@ -18,7 +18,7 @@ async fn main(_spawner: Spawner) { let mut config = config(); config.rcc.ls = LsConfig::default_lse(); - let p = embassy_stm32::init(config); + let p = init_with_config(config); info!("Hello World!"); let now = NaiveDate::from_ymd_opt(2020, 5, 15) diff --git a/tests/stm32/src/bin/sdmmc.rs b/tests/stm32/src/bin/sdmmc.rs index 54f55d2d6..a6bc117c0 100644 --- a/tests/stm32/src/bin/sdmmc.rs +++ b/tests/stm32/src/bin/sdmmc.rs @@ -20,7 +20,7 @@ bind_interrupts!(struct Irqs { async fn main(_spawner: Spawner) { info!("Hello World!"); - let p = embassy_stm32::init(config()); + let p = init(); let (mut sdmmc, mut dma, mut clk, mut cmd, mut d0, mut d1, mut d2, mut d3) = (p.SDIO, p.DMA2_CH3, p.PC12, p.PD2, p.PC8, p.PC9, p.PC10, p.PC11); diff --git a/tests/stm32/src/bin/spi.rs b/tests/stm32/src/bin/spi.rs index 0ffd0f653..53d44a94a 100644 --- a/tests/stm32/src/bin/spi.rs +++ b/tests/stm32/src/bin/spi.rs @@ -12,7 +12,7 @@ use embassy_stm32::time::Hertz; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); let mut spi_peri = peri!(p, SPI); diff --git a/tests/stm32/src/bin/spi_dma.rs b/tests/stm32/src/bin/spi_dma.rs index fd26d3f71..a1cbc0ed1 100644 --- a/tests/stm32/src/bin/spi_dma.rs +++ b/tests/stm32/src/bin/spi_dma.rs @@ -12,7 +12,7 @@ use embassy_stm32::time::Hertz; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); let mut spi_peri = peri!(p, SPI); diff --git a/tests/stm32/src/bin/stop.rs b/tests/stm32/src/bin/stop.rs index c1106bb2f..772bc527c 100644 --- a/tests/stm32/src/bin/stop.rs +++ b/tests/stm32/src/bin/stop.rs @@ -58,7 +58,7 @@ async fn async_main(spawner: Spawner) { config.rcc.hsi = Some(HSIPrescaler::DIV4); // 64 MHz HSI will need a /4 } - let p = embassy_stm32::init(config); + let p = init_with_config(config); info!("Hello World!"); let now = NaiveDate::from_ymd_opt(2020, 5, 15) diff --git a/tests/stm32/src/bin/timer.rs b/tests/stm32/src/bin/timer.rs index d86f54ad2..8719e7670 100644 --- a/tests/stm32/src/bin/timer.rs +++ b/tests/stm32/src/bin/timer.rs @@ -10,7 +10,7 @@ use embassy_time::{Instant, Timer}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let _p = embassy_stm32::init(config()); + let _p = init(); info!("Hello World!"); let start = Instant::now(); diff --git a/tests/stm32/src/bin/ucpd.rs b/tests/stm32/src/bin/ucpd.rs index c09334ec8..bd7b35d6b 100644 --- a/tests/stm32/src/bin/ucpd.rs +++ b/tests/stm32/src/bin/ucpd.rs @@ -102,12 +102,12 @@ async fn sink( #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); // Wire between PD0 and PA8 - let ucpd1 = Ucpd::new(p.UCPD1, Irqs {}, p.PA8, p.PB15); - let ucpd2 = Ucpd::new(p.UCPD2, Irqs {}, p.PD0, p.PD2); + let ucpd1 = Ucpd::new(p.UCPD1, Irqs {}, p.PA8, p.PB15, Default::default()); + let ucpd2 = Ucpd::new(p.UCPD2, Irqs {}, p.PD0, p.PD2, Default::default()); join( source(ucpd1, p.DMA1_CH1, p.DMA1_CH2), diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs index a6e34674d..53da30fff 100644 --- a/tests/stm32/src/bin/usart.rs +++ b/tests/stm32/src/bin/usart.rs @@ -11,7 +11,7 @@ use embassy_time::{block_for, Duration, Instant}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); // Arduino pins D0 and D1 diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs index 24e2b2896..266b81809 100644 --- a/tests/stm32/src/bin/usart_dma.rs +++ b/tests/stm32/src/bin/usart_dma.rs @@ -11,7 +11,7 @@ use embassy_stm32::usart::{Config, Uart}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); // Arduino pins D0 and D1 diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs index ea1e52358..98c7ef312 100644 --- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -18,7 +18,7 @@ const DMA_BUF_SIZE: usize = 256; #[embassy_executor::main] async fn main(spawner: Spawner) { - let p = embassy_stm32::init(config()); + let p = init(); info!("Hello World!"); // Arduino pins D0 and D1 diff --git a/tests/stm32/src/bin/wpan_ble.rs b/tests/stm32/src/bin/wpan_ble.rs index 82a540d45..fde1dfa9b 100644 --- a/tests/stm32/src/bin/wpan_ble.rs +++ b/tests/stm32/src/bin/wpan_ble.rs @@ -41,7 +41,7 @@ async fn main(spawner: Spawner) { let mut config = config(); config.rcc = WPAN_DEFAULT; - let p = embassy_stm32::init(config); + let p = init_with_config(config); info!("Hello World!"); let config = Config::default(); diff --git a/tests/stm32/src/bin/wpan_mac.rs b/tests/stm32/src/bin/wpan_mac.rs index fe53b8786..b65ace40f 100644 --- a/tests/stm32/src/bin/wpan_mac.rs +++ b/tests/stm32/src/bin/wpan_mac.rs @@ -34,7 +34,7 @@ async fn main(spawner: Spawner) { let mut config = config(); config.rcc = WPAN_DEFAULT; - let p = embassy_stm32::init(config); + let p = init_with_config(config); info!("Hello World!"); let config = Config::default(); diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs index 4e0231858..935a41ed2 100644 --- a/tests/stm32/src/common.rs +++ b/tests/stm32/src/common.rs @@ -699,3 +699,21 @@ pub fn config() -> Config { config } + +#[allow(unused)] +pub fn init() -> embassy_stm32::Peripherals { + init_with_config(config()) +} + +#[allow(unused)] +pub fn init_with_config(config: Config) -> embassy_stm32::Peripherals { + #[cfg(any(feature = "stm32wl55jc", feature = "stm32h755zi"))] + { + // Not in shared memory, but we're not running the second core, so it's fine + static SHARED_DATA: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); + embassy_stm32::init_primary(config, &SHARED_DATA) + } + + #[cfg(not(any(feature = "stm32wl55jc", feature = "stm32h755zi")))] + embassy_stm32::init(config) +}